Add lots of history functionality

adapt-navigator
Oystein Kristoffer Tveit 2021-08-03 22:02:42 +02:00
parent d82fcbe427
commit e8f42860af
19 changed files with 525 additions and 278 deletions

View File

@ -1,7 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:jisho_study_tool/bloc/database/database_bloc.dart'; import 'package:jisho_study_tool/bloc/database/database_bloc.dart';
import 'package:jisho_study_tool/models/history/kanji_result.dart'; import 'package:jisho_study_tool/models/history/kanji_query.dart';
import 'package:jisho_study_tool/models/history/search.dart';
import './kanji_event.dart'; import './kanji_event.dart';
import './kanji_state.dart'; import './kanji_state.dart';
@ -14,7 +15,6 @@ export './kanji_event.dart';
export './kanji_state.dart'; export './kanji_state.dart';
class KanjiBloc extends Bloc<KanjiEvent, KanjiState> { class KanjiBloc extends Bloc<KanjiEvent, KanjiState> {
DatabaseBloc _databaseBloc; DatabaseBloc _databaseBloc;
KanjiBloc(this._databaseBloc) : super(KanjiSearch(KanjiSearchType.Initial)); KanjiBloc(this._databaseBloc) : super(KanjiSearch(KanjiSearchType.Initial));
@ -24,34 +24,32 @@ class KanjiBloc extends Bloc<KanjiEvent, KanjiState> {
throw DatabaseNotConnectedException; throw DatabaseNotConnectedException;
(_databaseBloc.state as DatabaseConnected) (_databaseBloc.state as DatabaseConnected)
.database .database
.box<KanjiResult>() .box<Search>()
.put(KanjiResult( .put(Search(timestamp: DateTime.now())
kanji: kanji, ..kanjiQuery.target = KanjiQuery(
timestamp: DateTime.now(), kanji: kanji,
)); ));
} }
@override @override
Stream<KanjiState> mapEventToState(KanjiEvent event) Stream<KanjiState> mapEventToState(KanjiEvent event) async* {
async* {
if (event is GetKanji) { if (event is GetKanji) {
yield KanjiSearchLoading(); yield KanjiSearchLoading();
try { try {
addSearchToDB(event.kanjiSearchString); addSearchToDB(event.kanjiSearchString);
final kanji = await fetchKanji(event.kanjiSearchString); final kanji = await fetchKanji(event.kanjiSearchString);
if (kanji.found) yield KanjiSearchFinished(kanji: kanji); if (kanji.found)
else yield KanjiSearchError('Something went wrong'); yield KanjiSearchFinished(kanji: kanji);
else
yield KanjiSearchError('Something went wrong');
} on Exception { } on Exception {
yield KanjiSearchError('Something went wrong'); yield KanjiSearchError('Something went wrong');
} }
} else if (event is GetKanjiSuggestions) { } else if (event is GetKanjiSuggestions) {
final suggestions = kanjiSuggestions(event.searchString); final suggestions = kanjiSuggestions(event.searchString);
yield KanjiSearchKeyboard(KanjiSearchType.Keyboard, suggestions); yield KanjiSearchKeyboard(KanjiSearchType.Keyboard, suggestions);
} else if (event is ReturnToInitialState) { } else if (event is ReturnToInitialState) {
yield KanjiSearch(KanjiSearchType.Initial); yield KanjiSearch(KanjiSearchType.Initial);
} }

View File

@ -0,0 +1,18 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import './navigation_event.dart';
import './navigation_state.dart';
export './navigation_event.dart';
export './navigation_state.dart';
class NavigationBloc extends Bloc<NavigationEvent, NavigationState> {
NavigationBloc() : super(NavigationPage(0));
@override
Stream<NavigationState> mapEventToState(NavigationEvent event) async* {
if (event is ChangePage)
yield NavigationPage(event.pageNum);
}
}

View File

@ -0,0 +1,8 @@
abstract class NavigationEvent {
const NavigationEvent();
}
class ChangePage extends NavigationEvent {
final int pageNum;
const ChangePage(this.pageNum);
}

View File

@ -0,0 +1,9 @@
abstract class NavigationState {
const NavigationState();
}
class NavigationPage extends NavigationState {
final int pageNum;
const NavigationPage(this.pageNum);
}

View File

@ -3,7 +3,8 @@ import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:jisho_study_tool/models/history/search_string.dart'; import 'package:jisho_study_tool/models/history/search.dart';
import 'package:jisho_study_tool/models/history/word_query.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:jisho_study_tool/bloc/database/database_bloc.dart'; import 'package:jisho_study_tool/bloc/database/database_bloc.dart';
@ -14,7 +15,6 @@ part 'search_event.dart';
part 'search_state.dart'; part 'search_state.dart';
class SearchBloc extends Bloc<SearchEvent, SearchState> { class SearchBloc extends Bloc<SearchEvent, SearchState> {
DatabaseBloc _databaseBloc; DatabaseBloc _databaseBloc;
SearchBloc(this._databaseBloc) : super(SearchInitial()); SearchBloc(this._databaseBloc) : super(SearchInitial());
@ -24,12 +24,12 @@ class SearchBloc extends Bloc<SearchEvent, SearchState> {
throw DatabaseNotConnectedException; throw DatabaseNotConnectedException;
(_databaseBloc.state as DatabaseConnected) (_databaseBloc.state as DatabaseConnected)
.database .database
.box<SearchString>() .box<Search>()
.put(SearchString( .put(Search(timestamp: DateTime.now())
query: searchString, ..wordQuery.target = WordQuery(
timestamp: DateTime.now(), query: searchString,
)); ));
} }
@override @override
@ -42,7 +42,8 @@ class SearchBloc extends Bloc<SearchEvent, SearchState> {
try { try {
addSearchToDB(event.searchString); addSearchToDB(event.searchString);
final searchResults = await fetchJishoResults(event.searchString); final searchResults = await fetchJishoResults(event.searchString);
if (searchResults.meta.status == 200) yield SearchFinished(searchResults.data!); if (searchResults.meta.status == 200)
yield SearchFinished(searchResults.data!);
} on Exception { } on Exception {
yield SearchError('Something went wrong'); yield SearchError('Something went wrong');
} }

View File

@ -1,13 +1,16 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:jisho_study_tool/view/screens/loading.dart';
import 'package:mdi/mdi.dart'; import 'package:mdi/mdi.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:jisho_study_tool/objectbox.g.dart'; import 'package:jisho_study_tool/objectbox.g.dart';
import 'package:jisho_study_tool/bloc/database/database_bloc.dart'; import 'package:jisho_study_tool/bloc/database/database_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/bloc/search/search_bloc.dart'; import 'package:jisho_study_tool/bloc/search/search_bloc.dart';
import 'package:jisho_study_tool/bloc/navigation/navigation_bloc.dart';
import 'package:jisho_study_tool/view/screens/kanji/view.dart'; import 'package:jisho_study_tool/view/screens/kanji/view.dart';
import 'package:jisho_study_tool/view/screens/history.dart'; import 'package:jisho_study_tool/view/screens/history.dart';
@ -18,49 +21,26 @@ void main() => runApp(MyApp());
DatabaseBloc _databaseBloc = DatabaseBloc(); DatabaseBloc _databaseBloc = DatabaseBloc();
class MyApp extends StatelessWidget { class MyApp extends StatefulWidget {
@override @override
Widget build(BuildContext context) { _MyAppState createState() => _MyAppState();
return MaterialApp(
title: 'Jisho Study Tool',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MultiBlocProvider(
providers: [
BlocProvider(create: (context) => SearchBloc(_databaseBloc)),
BlocProvider(create: (context) => KanjiBloc(_databaseBloc)),
BlocProvider(create: (context) => _databaseBloc),
],
child: Home(),
),
);
}
} }
class Home extends StatefulWidget { class _MyAppState extends State<MyApp> {
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int selectedPage = 0;
late final Store _store; late final Store _store;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
getApplicationDocumentsDirectory() getApplicationDocumentsDirectory().then((dir) {
.then((dir) { _store = Store(
_store = Store( getObjectBoxModel(),
getObjectBoxModel(), directory: join(dir.path, 'objectbox'),
directory: join(dir.path, 'objectbox'), );
);
_databaseBloc.add(ConnectedToDatabase(_store)); _databaseBloc.add(ConnectedToDatabase(_store));
}); });
} }
@override @override
@ -72,14 +52,41 @@ class _HomeState extends State<Home> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<DatabaseBloc, DatabaseState>( return MaterialApp(
builder: (context, state) { title: 'Jisho Study Tool',
if (state is DatabaseDisconnected) { // TODO: Add color theme
return Center( theme: ThemeData(
child: CircularProgressIndicator(), primarySwatch: Colors.blue,
); ),
} home: MultiBlocProvider(
providers: [
BlocProvider(create: (context) => SearchBloc(_databaseBloc)),
BlocProvider(create: (context) => KanjiBloc(_databaseBloc)),
BlocProvider(create: (context) => _databaseBloc),
BlocProvider(create: (context) => NavigationBloc()),
],
child:
BlocBuilder<DatabaseBloc, DatabaseState>(builder: (context, state) {
if (state is DatabaseDisconnected)
return Container(
child: LoadingScreen(),
decoration: BoxDecoration(color: Colors.white),
);
return Home();
}),
),
);
}
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<NavigationBloc, NavigationState>(
builder: (context, state) {
int selectedPage = (state as NavigationPage).pageNum;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@ -89,12 +96,9 @@ class _HomeState extends State<Home> {
body: pages[selectedPage].content, body: pages[selectedPage].content,
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(
currentIndex: selectedPage, currentIndex: selectedPage,
onTap: (int index) { onTap: (int index) =>
setState(() { BlocProvider.of<NavigationBloc>(context).add(ChangePage(index)),
selectedPage = index; items: pages.map((p) => p.item).toList(),
});
},
items: navBar,
showSelectedLabels: false, showSelectedLabels: false,
showUnselectedLabels: false, showUnselectedLabels: false,
unselectedItemColor: Colors.blue, unselectedItemColor: Colors.blue,
@ -106,38 +110,15 @@ class _HomeState extends State<Home> {
} }
} }
final List<BottomNavigationBarItem> navBar = [
BottomNavigationBarItem(
label: 'Search',
icon: Icon(Icons.search),
),
BottomNavigationBarItem(
label: 'Kanji',
icon: Icon(
Mdi.ideogramCjk,
size: 30,
)),
BottomNavigationBarItem(
label: 'History',
icon: Icon(Icons.history),
),
BottomNavigationBarItem(
label: 'Memorize',
icon: Icon(Icons.bookmark),
),
BottomNavigationBarItem(
label: 'Settings',
icon: Icon(Icons.settings),
),
];
class _Page { class _Page {
Widget content; final Widget content;
Widget titleBar; final Widget titleBar;
final BottomNavigationBarItem item;
_Page({ const _Page({
required this.content, required this.content,
required this.titleBar, required this.titleBar,
required this.item,
}); });
} }
@ -145,21 +126,39 @@ final List<_Page> pages = [
_Page( _Page(
content: SearchView(), content: SearchView(),
titleBar: Text('Search'), titleBar: Text('Search'),
item: BottomNavigationBarItem(
label: 'Search',
icon: Icon(Icons.search),
),
), ),
_Page( _Page(
content: KanjiView(), content: KanjiView(),
titleBar: KanjiViewBar(), titleBar: KanjiViewBar(),
item: BottomNavigationBarItem(
label: 'Kanji', icon: Icon(Mdi.ideogramCjk, size: 30)),
), ),
_Page( _Page(
content: HistoryView(), content: HistoryView(),
titleBar: Text("History"), titleBar: Text("History"),
item: BottomNavigationBarItem(
label: 'History',
icon: Icon(Icons.history),
),
), ),
_Page( _Page(
content: Container(), content: Container(),
titleBar: Text("Memorization"), titleBar: Text("Saved"),
item: BottomNavigationBarItem(
label: 'Saved',
icon: Icon(Icons.bookmark),
),
), ),
_Page( _Page(
content: Container(), content: Container(),
titleBar: Text("Settings"), titleBar: Text("Settings"),
item: BottomNavigationBarItem(
label: 'Settings',
icon: Icon(Icons.settings),
),
), ),
]; ];

View File

@ -0,0 +1,13 @@
import 'package:objectbox/objectbox.dart';
@Entity()
class KanjiQuery {
int id;
String kanji;
KanjiQuery({
this.id = 0,
required this.kanji,
});
}

View File

@ -1,22 +0,0 @@
import 'package:objectbox/objectbox.dart';
@Entity()
class KanjiResult {
int id = 0;
@Property(type: PropertyType.date)
DateTime timestamp;
String kanji;
KanjiResult({
required this.timestamp,
required this.kanji,
});
@override
String toString() {
return "[${timestamp.toIso8601String()}] - $kanji";
}
}

View File

@ -0,0 +1,30 @@
import 'package:objectbox/objectbox.dart';
import './kanji_query.dart';
import './word_query.dart';
@Entity()
class Search {
int id;
@Property(type: PropertyType.date)
late final DateTime timestamp;
final wordQuery = ToOne<WordQuery>();
final kanjiQuery = ToOne<KanjiQuery>();
Search({
this.id = 0,
required this.timestamp
}); // {
bool isKanji() {
// // TODO: better error message
if (this.wordQuery.target == null && this.kanjiQuery.target == null)
throw Exception();
return this.wordQuery.target == null;
}
}

View File

@ -1,26 +0,0 @@
import 'package:objectbox/objectbox.dart';
import 'package:jisho_study_tool/models/history/word_result.dart';
@Entity()
class SearchString {
int id = 0;
@Property(type: PropertyType.date)
DateTime timestamp;
String query;
@Backlink()
final chosenResults = ToMany<WordResult>();
SearchString({
required this.timestamp,
required this.query,
});
@override
String toString() {
return "[${timestamp.toIso8601String()}] \"$query\"";
}
}

View File

@ -0,0 +1,19 @@
import 'package:objectbox/objectbox.dart';
import './word_result.dart';
@Entity()
class WordQuery {
int id;
String query;
// TODO: Link query with results that the user clicks onto.
@Backlink()
final chosenResults = ToMany<WordResult>();
WordQuery({
this.id = 0,
required this.query,
});
}

View File

@ -1,25 +1,21 @@
import 'package:objectbox/objectbox.dart'; import 'package:objectbox/objectbox.dart';
import 'package:jisho_study_tool/models/history/search_string.dart'; import 'package:jisho_study_tool/models/history/word_query.dart';
@Entity() @Entity()
class WordResult { class WordResult {
int id = 0; int id;
@Property(type: PropertyType.date) @Property(type: PropertyType.date)
DateTime timestamp; DateTime timestamp;
String word; String word;
final searchString = ToOne<SearchString>(); final searchString = ToOne<WordQuery>();
WordResult({ WordResult({
this.id = 0,
required this.timestamp, required this.timestamp,
required this.word, required this.word,
}); });
@override
String toString() {
return "[${timestamp.toIso8601String()}] - $word";
}
} }

View File

@ -3,54 +3,6 @@
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.", "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.", "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
"entities": [ "entities": [
{
"id": "1:8135239166970424087",
"lastPropertyId": "3:1930470268740402049",
"name": "KanjiResult",
"properties": [
{
"id": "1:2681934095975267680",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:4514526257378540330",
"name": "timestamp",
"type": 10
},
{
"id": "3:1930470268740402049",
"name": "kanji",
"type": 9
}
],
"relations": []
},
{
"id": "2:461492167249325765",
"lastPropertyId": "3:7573103520245228403",
"name": "SearchString",
"properties": [
{
"id": "1:4297905889790758495",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:4157902147911002923",
"name": "timestamp",
"type": 10
},
{
"id": "3:7573103520245228403",
"name": "query",
"type": 9
}
],
"relations": []
},
{ {
"id": "3:8314315977756262774", "id": "3:8314315977756262774",
"lastPropertyId": "4:7972948456299367594", "lastPropertyId": "4:7972948456299367594",
@ -78,21 +30,112 @@
"type": 11, "type": 11,
"flags": 520, "flags": 520,
"indexId": "1:6146948198859733323", "indexId": "1:6146948198859733323",
"relationTarget": "SearchString" "relationTarget": "WordQuery"
}
],
"relations": []
},
{
"id": "4:4256390943850643278",
"lastPropertyId": "3:1496429060084558178",
"name": "KanjiQuery",
"properties": [
{
"id": "1:2966275213904862677",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:3733952844232949036",
"name": "kanji",
"type": 9
}
],
"relations": []
},
{
"id": "5:3499538826755540666",
"lastPropertyId": "3:1154921921492752045",
"name": "WordQuery",
"properties": [
{
"id": "1:2582448470002735577",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:6622038022626247037",
"name": "query",
"type": 9
}
],
"relations": []
},
{
"id": "6:8118874861016646859",
"lastPropertyId": "5:818915488505962903",
"name": "Search",
"properties": [
{
"id": "1:3233720904924970047",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:7793044338609887616",
"name": "timestamp",
"type": 10
},
{
"id": "4:5737790291742758071",
"name": "wordQueryId",
"type": 11,
"flags": 520,
"indexId": "4:4174896839978600983",
"relationTarget": "WordQuery"
},
{
"id": "5:818915488505962903",
"name": "kanjiQueryId",
"type": 11,
"flags": 520,
"indexId": "5:5394995618034342416",
"relationTarget": "KanjiQuery"
} }
], ],
"relations": [] "relations": []
} }
], ],
"lastEntityId": "3:8314315977756262774", "lastEntityId": "6:8118874861016646859",
"lastIndexId": "1:6146948198859733323", "lastIndexId": "5:5394995618034342416",
"lastRelationId": "0:0", "lastRelationId": "1:2624712325077938293",
"lastSequenceId": "0:0", "lastSequenceId": "0:0",
"modelVersion": 5, "modelVersion": 5,
"modelVersionParserMinimum": 5, "modelVersionParserMinimum": 5,
"retiredEntityUids": [], "retiredEntityUids": [
"retiredIndexUids": [], 8135239166970424087,
"retiredPropertyUids": [], 461492167249325765
"retiredRelationUids": [], ],
"retiredIndexUids": [
2344626140411525437,
1957456749938325194
],
"retiredPropertyUids": [
2681934095975267680,
4514526257378540330,
1930470268740402049,
4297905889790758495,
4157902147911002923,
7573103520245228403,
1496429060084558178,
1154921921492752045,
2254834401134912797
],
"retiredRelationUids": [
2624712325077938293
],
"version": 1 "version": 1
} }

View File

@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
class DateDivider extends StatelessWidget {
final String? text;
final DateTime? date;
const DateDivider({this.text, this.date, Key? key}) : super(key: key);
String getHumanReadableDate(DateTime date) {
const Map<int, String> 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',
};
int day = date.day;
String month = monthTable[date.month]!;
int year = date.year;
return "$day. $month $year";
}
@override
Widget build(BuildContext context) {
Widget header = (this.text != null)
? Text(this.text!)
: (this.date != null)
? Text(getHumanReadableDate(this.date!))
: SizedBox.shrink();
return Container(
child: DefaultTextStyle.merge(
child: header,
style: TextStyle(color: Colors.white),
),
decoration: BoxDecoration(color: Colors.grey),
padding: EdgeInsets.symmetric(
vertical: 5,
horizontal: 10,
),
);
}
}

View File

@ -1,39 +1,78 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:jisho_study_tool/models/history/kanji_result.dart'; import 'package:jisho_study_tool/bloc/kanji/kanji_bloc.dart';
import 'package:jisho_study_tool/bloc/navigation/navigation_bloc.dart';
import 'package:jisho_study_tool/models/history/kanji_query.dart';
class _KanjiSearchItemHeader extends StatelessWidget { import './search_item.dart';
final KanjiResult result;
const _KanjiSearchItemHeader(this.result, {Key? key}) : super(key: key); class _KanjiBox extends StatelessWidget {
final String kanji;
const _KanjiBox(this.kanji);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Text("[KANJI] ${result.kanji} - ${result.timestamp.toIso8601String()}"); return IntrinsicHeight(
child: AspectRatio(
aspectRatio: 1,
child: Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(10.0),
),
child: Center(
child: FittedBox(
child: Text(
kanji,
style: TextStyle(
color: Colors.black,
fontSize: 25,
),
),
),
),
),
),
);
} }
} }
class KanjiSearchItem extends StatelessWidget { class KanjiSearchItem extends StatelessWidget {
final KanjiResult result; final KanjiQuery result;
final DateTime timestamp;
const KanjiSearchItem(this.result,{Key? key}) : super(key: key); const KanjiSearchItem({
required this.result,
required this.timestamp,
Key? key,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Slidable( return Slidable(
child: ListTile(title: _KanjiSearchItemHeader(result)), child: SearchItem(
onTap: () {
BlocProvider.of<NavigationBloc>(context).add(ChangePage(1));
BlocProvider.of<KanjiBloc>(context).add(GetKanji(this.result.kanji));
},
time: timestamp,
search: _KanjiBox(result.kanji),
),
actionPane: SlidableScrollActionPane(), actionPane: SlidableScrollActionPane(),
secondaryActions: [ secondaryActions: [
IconSlideAction( IconSlideAction(
caption: "Favourite", caption: "Favourite",
color: Colors.yellow, color: Colors.yellow,
icon: Icons.star icon: Icons.star,
), ),
IconSlideAction( IconSlideAction(
caption: "Delete", caption: "Delete",
color: Colors.red, color: Colors.red,
icon: Icons.delete icon: Icons.delete,
) ),
], ],
); );
} }

View File

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:jisho_study_tool/bloc/navigation/navigation_bloc.dart';
import 'package:jisho_study_tool/bloc/search/search_bloc.dart';
import 'package:jisho_study_tool/models/history/word_query.dart';
import './search_item.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(
actionPane: SlidableScrollActionPane(),
secondaryActions: [
IconSlideAction(
caption: "Delete", color: Colors.red, icon: Icons.delete)
],
child: SearchItem(
onTap: () {
BlocProvider.of<NavigationBloc>(context).add(ChangePage(0));
BlocProvider.of<SearchBloc>(context).add(GetSearchResults(this.search.query));
},
time: timestamp,
search: Text(search.query),
),
);
}
}

View File

@ -1,41 +1,39 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:jisho_study_tool/models/history/search_string.dart';
class SearchItemHeader extends StatelessWidget { class SearchItem extends StatelessWidget {
final SearchString _search; final DateTime time;
final Widget search;
final void Function()? onTap;
const SearchItemHeader(this._search, {Key? key}) : super(key: key); const SearchItem({
required this.time,
required this.search,
this.onTap,
Key? key,
}) : super(key: key);
String getTime() {
final hours = this.time.hour.toString().padLeft(2, '0');
final mins = this.time.minute.toString().padLeft(2, '0');
return "$hours:$mins";
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
child: Text("[SEARCH] ${_search.query} - ${_search.timestamp.toString()}"), child: ListTile(
); onTap: onTap,
} contentPadding: EdgeInsets.zero,
} title: Row(
children: [
class SearchItem extends StatelessWidget { Padding(
final SearchString _search; padding: EdgeInsets.symmetric(horizontal: 20),
child: Text(getTime()),
const SearchItem(this._search, {Key? key}) : super(key: key); ),
search,
@override ],
Widget build(BuildContext context) { ),
return Slidable( ),
actionPane: SlidableScrollActionPane(),
secondaryActions: [
IconSlideAction(
caption: "Delete",
color: Colors.red,
icon: Icons.delete
)
],
child: ExpansionTile(
title: SearchItemHeader(_search),
expandedAlignment: Alignment.topCenter,
children: [ListTile(title: Text(_search.timestamp.toIso8601String()),)],
)
); );
} }
} }

View File

@ -14,14 +14,12 @@ class KanjiSearchBar extends StatefulWidget {
enum TextFieldButton {clear, paste} enum TextFieldButton {clear, paste}
class _KanjiSearchBarState extends State<KanjiSearchBar> { class _KanjiSearchBarState extends State<KanjiSearchBar> {
FocusNode focus = new FocusNode(); final TextEditingController textController = new TextEditingController();
TextEditingController textController = new TextEditingController();
TextFieldButton button = TextFieldButton.paste; TextFieldButton button = TextFieldButton.paste;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// focus.addListener(_onFocusChange);
} }
void _getKanjiSuggestions(String text) => void _getKanjiSuggestions(String text) =>
@ -57,10 +55,8 @@ class _KanjiSearchBarState extends State<KanjiSearchBar> {
return TextField( return TextField(
controller: textController, controller: textController,
onChanged: (text) => _getKanjiSuggestions(text), onChanged: (text) => _getKanjiSuggestions(text),
onSubmitted: (text) => {}, onSubmitted: (_) => {},
// BlocProvider.of<KanjiBloc>(context).add(GetKanji(text)),
decoration: new InputDecoration( decoration: new InputDecoration(
prefixIcon: Icon(Icons.search), prefixIcon: Icon(Icons.search),
hintText: 'Search', hintText: 'Search',
fillColor: Colors.white, fillColor: Colors.white,

View File

@ -1,35 +1,74 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:jisho_study_tool/bloc/database/database_bloc.dart'; import 'package:jisho_study_tool/bloc/database/database_bloc.dart';
import 'package:jisho_study_tool/models/history/kanji_result.dart'; import 'package:jisho_study_tool/models/history/search.dart';
import 'package:jisho_study_tool/models/history/search_string.dart';
import 'package:jisho_study_tool/view/components/history/kanji_search_item.dart'; import 'package:jisho_study_tool/view/components/history/kanji_search_item.dart';
import 'package:jisho_study_tool/view/components/history/search_item.dart'; import 'package:jisho_study_tool/view/components/history/phrase_search_item.dart';
import 'package:jisho_study_tool/view/components/history/date_divider.dart';
import 'package:jisho_study_tool/objectbox.g.dart';
class HistoryView extends StatelessWidget { class HistoryView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// return ListView.builder(
// itemBuilder: (context, index) => ListTile(),
// );
return BlocBuilder<DatabaseBloc, DatabaseState>( return BlocBuilder<DatabaseBloc, DatabaseState>(
builder: (context, state) { builder: (context, state) {
if (state is DatabaseDisconnected) if (state is DatabaseDisconnected)
throw DatabaseNotConnectedException(); throw DatabaseNotConnectedException();
return ListView(
children: (state as DatabaseConnected)
.database
.box<SearchString>()
.getAll()
.map((e) => SearchItem(e) as Widget)
.toList()
+ (state as DatabaseConnected) return StreamBuilder(
.database stream: ((state as DatabaseConnected).database.box<Search>().query()
.box<KanjiResult>() ..order(Search_.timestamp, flags: Order.descending))
.getAll() .watch(triggerImmediately: true)
.map((e) => KanjiSearchItem(e) as Widget) .map((query) => query.find()),
.toList(), builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) return Container();
return ListView.separated(
itemCount: snapshot.data.length + 1,
itemBuilder: (context, index) {
if (index == 0) return Container();
Search search = snapshot.data[index - 1];
if (search.isKanji()) {
return KanjiSearchItem(
result: search.kanjiQuery.target!,
timestamp: search.timestamp,
);
}
return PhraseSearchItem(
search: search.wordQuery.target!,
timestamp: search.timestamp,
);
},
separatorBuilder: (context, index) {
Function roundToDay = (DateTime date) =>
DateTime(date.year, date.month, date.day);
Search search = snapshot.data[index];
DateTime searchDate = roundToDay(search.timestamp);
bool newDate = true;
if (index != 0) {
Search prevSearch = snapshot.data[index - 1];
DateTime prevSearchDate = roundToDay(prevSearch.timestamp);
newDate = prevSearchDate != searchDate;
}
if (newDate) {
if (searchDate == roundToDay(DateTime.now()))
return DateDivider(text: "Today");
else if (searchDate ==
roundToDay(
DateTime.now().subtract(const Duration(days: 1))))
return DateDivider(text: "Yesterday");
return DateDivider(date: searchDate);
}
return Divider();
},
);
},
); );
}, },
); );