Skip to content

Commit

Permalink
Use riverpod for query
Browse files Browse the repository at this point in the history
  • Loading branch information
petlyh committed Nov 17, 2024
1 parent 6c97c5c commit 3b47cb9
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 218 deletions.
16 changes: 7 additions & 9 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import "package:dynamic_color/dynamic_color.dart";
import "package:flutter/foundation.dart";
import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:jsdict/providers/query_provider.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"
hide ChangeNotifierProvider, Provider;
import "package:jsdict/providers/theme_provider.dart";
import "package:jsdict/screens/search/search_screen.dart";
import "package:jsdict/singletons.dart";
Expand Down Expand Up @@ -76,12 +77,9 @@ class JsDictApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => QueryProvider()),
ChangeNotifierProvider(create: (_) => ThemeProvider()),
],
builder: (lightDynamic, darkDynamic) => ProviderScope(
child: MultiProvider(
providers: [ChangeNotifierProvider(create: (_) => ThemeProvider())],
builder: (context, _) {
final themeProvider = Provider.of<ThemeProvider>(context);

Expand All @@ -100,8 +98,8 @@ class JsDictApp extends StatelessWidget {
home: const SearchScreen(),
);
},
);
},
),
),
);
}
}
13 changes: 9 additions & 4 deletions lib/packages/intent_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,19 @@ import "package:jsdict/screens/word_details/word_details_screen.dart";
import "package:jsdict/widgets/error_indicator.dart";
import "package:receive_sharing_intent/receive_sharing_intent.dart";

void useIntentHandler({required TabController tabController}) => use(
_IntentHandlerHook(tabController),
void useIntentHandler({
required TabController tabController,
required QueryController queryController,
}) =>
use(
_IntentHandlerHook(tabController, queryController),
);

class _IntentHandlerHook extends Hook<void> {
const _IntentHandlerHook(this.tabController);
const _IntentHandlerHook(this.tabController, this.queryController);

final TabController tabController;
final QueryController queryController;

@override
HookState<void, _IntentHandlerHook> createState() =>
Expand Down Expand Up @@ -100,7 +105,7 @@ class _IntentHandlerHookState extends HookState<void, _IntentHandlerHook> {
}

hook.tabController.index = _tabIndex(query);
QueryProvider.of(context).query = removeTags(query);
hook.queryController.update(removeTags(query));
popAll(context);
}

Expand Down
75 changes: 11 additions & 64 deletions lib/providers/query_provider.dart
Original file line number Diff line number Diff line change
@@ -1,72 +1,19 @@
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart" hide Provider;
import "package:jsdict/packages/remove_tags.dart";
import "package:provider/provider.dart";

class QueryProvider extends ChangeNotifier {
static QueryProvider of(BuildContext context) {
return Provider.of<QueryProvider>(context, listen: false);
}

TextEditingController searchController = TextEditingController();

String _query = "";
String get query => _query;
final queryProvider =
NotifierProvider<QueryController, String>(QueryController.new);

set query(String text) {
searchController.text = text;
updateQuery();
}

void sanitizeText() {
searchController.text = removeTypeTags(searchController.text)
.trim()
.replaceAll(RegExp(r"\s+"), " ");
}

void updateQuery() {
sanitizeText();
_query = searchController.text;
notifyListeners();
}

void updateQueryIfChanged() {
if (_query != searchController.text) {
updateQuery();
}
}

void addTag(String tag) {
sanitizeText();
if (searchController.text.isNotEmpty) {
searchController.text += " ";
}
searchController.text += "#$tag";
}

void clearTags() {
searchController.text = removeTags(searchController.text);
sanitizeText();
}

void insertText(String text) {
final selection = searchController.selection;
final selectionStart = selection.baseOffset;
class QueryController extends Notifier<String> {
@override
String build() => "";

if (selectionStart == -1) {
searchController.text += text;
return;
void update(String newQuery) {
if (state != newQuery) {
state = _sanitizeQuery(newQuery);
}

final newText = searchController.text
.replaceRange(selectionStart, selection.extentOffset, text);
searchController.text = newText;
searchController.selection =
TextSelection.collapsed(offset: selectionStart + 1);
}

@override
void dispose() {
searchController.dispose();
super.dispose();
}
String _sanitizeQuery(String query) =>
removeTypeTags(query).trim().replaceAll(RegExp(r"\s+"), " ");
}
12 changes: 6 additions & 6 deletions lib/screens/search/result_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "package:collection/collection.dart";
import "package:flutter/foundation.dart";
import "package:flutter/material.dart";
import "package:flutter_hooks/flutter_hooks.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart";
import "package:jsdict/jp_text.dart";
import "package:jsdict/models/models.dart";
Expand All @@ -20,7 +21,6 @@ import "package:jsdict/widgets/items/name_item.dart";
import "package:jsdict/widgets/items/sentence_item.dart";
import "package:jsdict/widgets/items/word_item.dart";
import "package:jsdict/widgets/link_span.dart";
import "package:provider/provider.dart";

class ResultPageScreen<T extends ResultType> extends StatelessWidget {
const ResultPageScreen({required this.query});
Expand Down Expand Up @@ -239,13 +239,13 @@ class _ConversionText extends StatelessWidget {
}
}

class _CorrectionText extends StatelessWidget {
class _CorrectionText extends ConsumerWidget {
const _CorrectionText({required this.correction});

final Correction correction;

@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final textColor = Theme.of(context).textTheme.bodyLarge!.color;

return _paddedSliverAdapter(
Expand All @@ -267,9 +267,9 @@ class _CorrectionText extends StatelessWidget {
context: context,
text: correction.original,
bold: true,
onTap: () => Provider.of<QueryProvider>(context, listen: false)
..searchController.text = correction.original
..updateQuery(),
onTap: () => ref
.read(queryProvider.notifier)
.update(correction.original),
),
],
],
Expand Down
95 changes: 51 additions & 44 deletions lib/screens/search/search_screen.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import "package:flutter/material.dart";
import "package:flutter_hooks/flutter_hooks.dart";
import "package:jsdict/jp_text.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:jsdict/models/models.dart";
import "package:jsdict/packages/intent_handler.dart";
import "package:jsdict/packages/navigation.dart";
Expand All @@ -9,56 +9,46 @@ import "package:jsdict/screens/search/result_page.dart";
import "package:jsdict/screens/search_options/radical_search_screen.dart";
import "package:jsdict/screens/search_options/tag_selection_screen.dart";
import "package:jsdict/screens/settings_screen.dart";
import "package:provider/provider.dart";
import "package:jsdict/widgets/search_field.dart";

class SearchScreen extends HookWidget {
class SearchScreen extends HookConsumerWidget {
const SearchScreen();

static const _placeholder = Center(
child: Text("JS-Dict", style: TextStyle(fontSize: 32)),
);

@override
Widget build(BuildContext context) {
final queryProvider = QueryProvider.of(context);
Widget build(BuildContext context, WidgetRef ref) {
final searchController = useTextEditingController(text: "");

final searchController = queryProvider.searchController;
final searchFocusNode = useFocusNode();
// Update search field text when query is changed from somewhere else.
ref.listen(queryProvider, (_, query) => searchController.text = query);

final tabController = useTabController(initialLength: 4);

useIntentHandler(tabController: tabController);
useIntentHandler(
tabController: tabController,
queryController: ref.read(queryProvider.notifier),
);

return Scaffold(
resizeToAvoidBottomInset: false,
floatingActionButton: FloatingActionButton(
onPressed: pushScreen(context, const RadicalSearchScreen()),
onPressed: () =>
pushScreen(context, RadicalSearchScreen(searchController.text))
.call(),
tooltip: "Radicals",
child: const Text("部", style: TextStyle(fontSize: 20)),
),
appBar: AppBar(
title: TextField(
style: jpTextStyle,
focusNode: searchFocusNode,
title: SearchField(
controller: searchController,
onSubmitted: (_) => queryProvider.updateQuery(),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.search),
border: InputBorder.none,
hintText: "Search...",
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
searchFocusNode.requestFocus();
searchController.clear();
},
tooltip: "Clear",
),
),
onSubmitted: ref.read(queryProvider.notifier).update,
showSearchIcon: true,
focusOnClear: true,
),
actions: [
IconButton(
onPressed: pushScreen(context, const TagSelectionScreen()),
onPressed: () =>
pushScreen(context, TagSelectionScreen(searchController.text))
.call(),
icon: const Icon(Icons.tag),
tooltip: "Tags",
),
Expand All @@ -80,19 +70,36 @@ class SearchScreen extends HookWidget {
],
),
),
body: Consumer<QueryProvider>(
builder: (_, provider, __) => provider.query.isEmpty
? _placeholder
: TabBarView(
controller: tabController,
children: [
ResultPage<Word>(query: provider.query, key: UniqueKey()),
ResultPage<Kanji>(query: provider.query, key: UniqueKey()),
ResultPage<Name>(query: provider.query, key: UniqueKey()),
ResultPage<Sentence>(query: provider.query, key: UniqueKey()),
],
),
),
body: _SearchScreenContent(tabController: tabController),
);
}
}

class _SearchScreenContent extends ConsumerWidget {
const _SearchScreenContent({required this.tabController});

final TabController tabController;

static const _placeholder = Center(
child: Text("JS-Dict", style: TextStyle(fontSize: 32)),
);

@override
Widget build(BuildContext context, WidgetRef ref) {
final query = ref.watch(queryProvider);

if (query.isEmpty) {
return _placeholder;
}

return TabBarView(
controller: tabController,
children: [
ResultPage<Word>(query: query, key: ValueKey((query, Word))),
ResultPage<Kanji>(query: query, key: ValueKey((query, Kanji))),
ResultPage<Name>(query: query, key: ValueKey((query, Name))),
ResultPage<Sentence>(query: query, key: ValueKey((query, Sentence))),
],
);
}
}
Loading

0 comments on commit 3b47cb9

Please sign in to comment.