From 29ea6d52c76e8cc2ad7eba6ce1628e20c6404065 Mon Sep 17 00:00:00 2001 From: yustas Date: Wed, 5 Feb 2025 22:44:41 +0200 Subject: [PATCH] Search improvements --- lib/i18n/ua.dart | 4 +-- lib/models/content.dart | 1 + lib/models/search_data.dart | 18 ++++++++----- lib/repository/content.dart | 48 +++++++++++++++++++++++++++-------- lib/utils/search.dart | 30 ++++++++++++++-------- lib/widgets/content_list.dart | 9 ++----- lib/widgets/search_list.dart | 27 ++++++++++++++------ 7 files changed, 91 insertions(+), 46 deletions(-) diff --git a/lib/i18n/ua.dart b/lib/i18n/ua.dart index 5faebaf..5c07995 100644 --- a/lib/i18n/ua.dart +++ b/lib/i18n/ua.dart @@ -2,5 +2,5 @@ const String appTitle = "Правопис"; const String searchTitle = "Пошук"; const String searchHint = "літера або слово"; const String searchNotFond = "нічого не знайдено"; -const String searchTry = "Cпробуйте:"; -const searchExamples = ["архі", "ї", "апостроф", "чергування"]; +const String searchTry = "Cпробуйте"; +const searchExamples = ["архі", "ї", "апостроф", "чергування", "при", "крапка"]; diff --git a/lib/models/content.dart b/lib/models/content.dart index 2055e5d..ae04e63 100644 --- a/lib/models/content.dart +++ b/lib/models/content.dart @@ -15,4 +15,5 @@ class Content { final int pos; get numeration => prefix.isEmpty ? '' : '$prefix '; + get name => numeration + data; } diff --git a/lib/models/search_data.dart b/lib/models/search_data.dart index a11ced3..0d4d9e9 100644 --- a/lib/models/search_data.dart +++ b/lib/models/search_data.dart @@ -1,11 +1,15 @@ -class SearchData { +import 'content.dart'; + +class SearchData extends Content { const SearchData({ - required this.contentId, - required this.data, - required this.prefix, + required super.data, + required super.id, + required super.level, + required super.parent, + required super.pos, + required super.prefix, + required this.path, }); - final int contentId; - final String data; - final String prefix; + final String path; } diff --git a/lib/repository/content.dart b/lib/repository/content.dart index 023cfbe..87a278e 100644 --- a/lib/repository/content.dart +++ b/lib/repository/content.dart @@ -5,6 +5,7 @@ import 'package:mova/models/article.dart'; import 'package:mova/models/page_data.dart'; import '../i18n/ua.dart'; +import '../models/search_data.dart'; Iterable uniqueList(Iterable list) { return list.toSet().toList(); @@ -92,18 +93,19 @@ Content contentRow(Map row) { ); } -Content searchRow(Map row) { - return Content( +SearchData searchRow(Map row, List content) { + return SearchData( id: row['content_id'] as int, level: row['content_level'] as int, - data: row['content_data'] as String, - prefix: row['content_prefix'] as String, parent: row['content_parent'] as int, pos: row['content_pos'] as int, + prefix: row['content_prefix'] as String, + data: row['content_data'] as String, + path: content.firstWhere((c) => c.id == row['content_parent']).name, ); } -Future> loadContent({int parent = 0}) async { +Future> loadContentByParent({int parent = 0}) async { Database db = await initDb(); List rows = await db.query( @@ -111,7 +113,22 @@ Future> loadContent({int parent = 0}) async { columns: ['id', 'level', 'parent', 'data', 'prefix', 'pos'], where: 'parent = ?', whereArgs: [parent], - orderBy: 'pos', + ); + + return rows.isNotEmpty + ? rows.map((row) => contentRow(row)).toList() + : List.empty(); +} + +Future> loadContentById(Iterable ids) async { + Database db = await initDb(); + Iterable placeholders = ids.map((id) => '?'); + + List rows = await db.query( + 'content', + columns: ['id', 'level', 'parent', 'data', 'prefix', 'pos'], + where: 'id IN (${placeholders.join(',')})', + whereArgs: [...ids], ); return rows.isNotEmpty @@ -158,7 +175,7 @@ Future> loadArticles({int parentId = 0}) async { } Future loadPage({int parentContentId = 0}) async { - List content = await loadContent(parent: parentContentId); + List content = await loadContentByParent(parent: parentContentId); List contentIds = content.map((c) => c.id).toList(); List
articles = contentIds.isEmpty @@ -168,7 +185,7 @@ Future loadPage({int parentContentId = 0}) async { return PageData(content: content, articles: articles); } -Future> findContent({String needle = ''}) async { +Future> findContent({String needle = ''}) async { var db = await initDb(); if (needle.isEmpty) { return List.empty(); @@ -197,6 +214,7 @@ Future> findContent({String needle = ''}) async { where: where, whereArgs: whereArgs, groupBy: 'content_id', + ); Iterable ids = rowsExactly.map((row) => row['content_id'] as int); @@ -224,10 +242,17 @@ Future> findContent({String needle = ''}) async { ); List rows = rowsExactly + rowsStarFrom; + Iterable contentIds = rows.isNotEmpty + ? rows.map((row) => row['content_parent'] as int) + : []; - return rows.isNotEmpty - ? rows.map((row) => searchRow(row)).toList() + List content = contentIds.isNotEmpty + ? await loadContentById(contentIds) : List.empty(); + + return rows.isNotEmpty + ? rows.map((row) => searchRow(row, content)).toList() + : List.empty(); } const homeContent = Content( @@ -239,11 +264,12 @@ const homeContent = Content( prefix: '', ); -const searchContent = Content( +const searchContent = SearchData( id: 0, level: 0, data: searchTitle, parent: 0, pos: 0, prefix: '', + path: "" ); diff --git a/lib/utils/search.dart b/lib/utils/search.dart index 9291564..2f69dc2 100644 --- a/lib/utils/search.dart +++ b/lib/utils/search.dart @@ -5,7 +5,7 @@ import 'package:mova/widgets/error.dart'; import 'package:mova/widgets/loading.dart'; import 'package:mova/widgets/search_list.dart'; -import '../models/content.dart'; +import '../models/search_data.dart'; class ContentSearchDelegate extends SearchDelegate { ContentSearchDelegate() : super(searchFieldLabel: searchHint); @@ -45,11 +45,11 @@ class ContentSearchDelegate extends SearchDelegate { @override Widget buildSuggestions(BuildContext context) { - final Future> searchData = findContent(needle: query); + final Future> searchData = findContent(needle: query); - return FutureBuilder>( + return FutureBuilder>( future: searchData, - builder: (BuildContext context, AsyncSnapshot> snapshot) { + builder: (BuildContext context, AsyncSnapshot> snapshot) { if (snapshot.hasData) { if (snapshot.data!.isNotEmpty) { return SearchList(results: snapshot.data!); @@ -80,12 +80,20 @@ Widget SearchHint(BuildContext context, callback) { child: Column( children:[ const SizedBox(height: 20,), - const Text(searchTry), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ...examples.map((text) => SearchExample(context, text, callback)) - ], + Text( + searchTry, + style: Theme.of(context).textTheme.headlineLarge!.copyWith( + color: Theme.of(context).hintColor, + ), + ), + const SizedBox(height: 20,), + Expanded( + child: GridView.count( + crossAxisCount: 4, + children: List.from([ + ...examples.map((text) => SearchExample(context, text, callback)) + ],), + ), ) ] ), @@ -97,7 +105,7 @@ Widget SearchExample(BuildContext context, text, onTap) { child: Card( child: Padding( padding: const EdgeInsets.all(8.0), - child: Text(text), + child: Center(child: Text(text, textAlign: TextAlign.center,)), ), ), onTap: () => onTap(text), diff --git a/lib/widgets/content_list.dart b/lib/widgets/content_list.dart index 60a9d35..62b9344 100644 --- a/lib/widgets/content_list.dart +++ b/lib/widgets/content_list.dart @@ -27,7 +27,7 @@ class ContentList extends StatelessWidget { padding: const EdgeInsets.fromLTRB(16.0, 10.0, 16.0, 0), child: Container( decoration: BoxDecoration( - //color: Theme.of(context).colorScheme.surfaceContainerLowest, +// color: Theme.of(context).colorScheme.surfaceContainerLowest, borderRadius: BorderRadius.circular(20), ), padding: const EdgeInsets.symmetric(horizontal: 16.0), @@ -39,10 +39,6 @@ class ContentList extends StatelessWidget { // ? Text(content[index].numeration, style: Theme.of(context).textTheme.titleLarge) // : Text('•', style: Theme.of(context).textTheme.titleLarge); - String numerationText = content[index].numeration.toString().isNotEmpty - ? content[index].numeration + ' ' - : ''; - return InkWell( onTap: () { openContent( @@ -55,7 +51,6 @@ class ContentList extends StatelessWidget { decoration: BoxDecoration( border: Border( bottom: BorderSide( - //color: Color.fromARGB(70, 150, 150, 150) color: Theme.of(context).colorScheme.outlineVariant, ))), child: Row( @@ -71,7 +66,7 @@ class ContentList extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('$numerationText${content[index].data}', + Text('${content[index].name}', style: Theme.of(context).textTheme.titleLarge) ], ), diff --git a/lib/widgets/search_list.dart b/lib/widgets/search_list.dart index 4fbb289..afe9c4e 100644 --- a/lib/widgets/search_list.dart +++ b/lib/widgets/search_list.dart @@ -2,12 +2,13 @@ import 'package:flutter/material.dart'; import 'package:mova/repository/content.dart'; import '../models/content.dart'; +import '../models/search_data.dart'; import '../screens/pravopys.dart'; class SearchList extends StatelessWidget { const SearchList({super.key, required this.results}); - final List results; + final List results; @override Widget build(BuildContext context) { @@ -37,21 +38,31 @@ class SearchList extends StatelessWidget { ); }, child: Container( - padding: const EdgeInsets.symmetric(vertical: 6.0), - decoration: const BoxDecoration( + padding: const EdgeInsets.all(12.0), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerLowest, + borderRadius: BorderRadius.circular(20), border: Border( bottom: BorderSide( - color: Color.fromARGB(70, 150, 150, 150)))), + color: Theme.of(context).colorScheme.outlineVariant, + ))), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Column( - children: [Text(results[index].prefix)], - ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: [Text(results[index].data)], + children: [ + Text( + results[index].path, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + + ), + const SizedBox(height: 10,), + Text(results[index].name), + ], ), ), const Column(