sqlite3_simple 1.0.6
sqlite3_simple: ^1.0.6 copied to clipboard
基于 Simple (支持中文和拼音的 SQLite fts5 全文搜索扩展) 和 sqlite3.dart 的 Flutter 库,用于 SQLite 中文和拼音全文搜索。
import 'dart:async';
import 'package:extended_text/extended_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'component/dropdown.dart';
import 'component/highlight_text.dart';
import 'data/db_manager.dart';
import 'data/impl/sqflite_common_ffi_impl.dart';
import 'data/impl/sqlite3_impl.dart';
import 'data/main_table_dao.dart';
import 'data/main_table_row.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState<T> extends State<MyApp> {
IDbManager? dbManager;
IMainTableDao get dao => dbManager!.dao;
List<MainTableRowUiModel>? results;
@override
void initState() {
super.initState();
searchController.addListener(onSearchValueChanged);
initDbManger().then((_) => setState(() {}));
}
/// 初始化数据库
Future<void> initDbManger() async {
results = null;
if (dbManager != null) dbManager!.dispose();
setState(() {});
dbManager = dbImplement == DbImplement.sqlite3
? Sqlite3DbManager()
: SqfliteCommonFfiDbManager();
await dbManager!.init();
await dao.insertRandomData(30);
results = await _toMainTableRowUiModel(await dao.selectAll());
}
/// 转为 UI 显示的数据类
Future<List<MainTableRowUiModel>> _toMainTableRowUiModel(
List<MainTableRow> rows) async {
final count = await dao.selectCount();
return rows
.map((r) => MainTableRowUiModel(
id: r.id,
idFormatted: "${r.id}".padLeft("$count".length, "0"),
title: r.title,
content: r.content,
insertDate: r.insertDate,
))
.toList();
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return MaterialApp(
debugShowCheckedModeBanner: false,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('zh', 'CN')],
// 通过国际化设置中文环境以让 Flutter 使用正确的中文字体
home: Scaffold(
appBar: AppBar(
title: const Text('Simple 分词器 示例',
style: TextStyle(fontWeight: FontWeight.bold)),
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
),
body: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
buildSearchBar(),
buildSearchOption(),
Expanded(
child: AnimatedSwitcher(
duration: kThemeChangeDuration,
child: results != null
? buildListView()
: const CircularProgressIndicator(),
),
),
],
),
),
),
);
}
/// 搜索栏
Widget buildSearchBar() {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: P.middle, right: P.middle, top: P.middle),
child: TapRegion(
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
child: SearchBar(
controller: searchController,
leading: const Padding(
padding: EdgeInsets.symmetric(horizontal: 4),
child: Icon(Icons.search),
),
trailing: [
if (showClearButton)
IconButton(
onPressed: () => searchController.text = "",
icon: const Icon(Icons.clear))
],
elevation: const WidgetStatePropertyAll(0),
shape: WidgetStatePropertyAll(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8))),
),
),
),
)
],
);
}
var showClearButton = false;
final searchController = SearchController();
Future<void> onSearchValueChanged() async {
final value = searchController.text;
showClearButton = value.isNotEmpty;
results = await _toMainTableRowUiModel(showClearButton
? await dao.search(value.trim(), tokenizer)
: await dao.selectAll());
setState(() {});
}
/// 搜索栏下方选项
Widget buildSearchOption() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: P.middle),
child: Row(
children: [
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: P.middle),
scrollDirection: Axis.horizontal,
child: Row(
children: [
Dropdown<Tokenizer>(
label: "分词器:",
initValue: tokenizer,
map: tokenizer2uiString,
onChanged: (value) => setState(() {
tokenizer = value!;
onSearchValueChanged();
}),
),
const SizedBox(width: P.small),
Dropdown<DbImplement>(
label: "数据库实现:",
initValue: dbImplement,
map: implement2uiString,
onChanged: (value) => setState(() {
dbImplement = value!;
initDbManger().then((_) => onSearchValueChanged());
}),
),
],
),
),
),
const SizedBox(width: P.small),
IconButton(
style: ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: WidgetStatePropertyAll(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(P.small)))),
onPressed: () => setState(() {
results = null;
dao.updateAll().then((_) => onSearchValueChanged());
}),
icon: const Icon(Icons.refresh),
)
],
),
);
}
static const tokenizer2uiString = {
Tokenizer.jieba: "结巴",
Tokenizer.simple: "Simple"
};
Tokenizer tokenizer = tokenizer2uiString.keys.first;
static const implement2uiString = {
DbImplement.sqlite3: "sqlite3",
DbImplement.sqfliteCommonFfi: "sqflite_common_ffi",
};
DbImplement dbImplement = implement2uiString.keys.first;
/// 搜索结果
Widget buildListView() {
return ListView.builder(
itemCount: results!.length,
itemBuilder: (context, index) {
final r = results![index];
return Material( // 水波纹特效超出列表:https://github.com/flutter/flutter/issues/73315
child: InkWell(
onTap: () => showDialog(
context: context, builder: (context) => buildDialog(context, r)),
child: Padding(
padding: const EdgeInsets.only(
left: P.middle,
right: P.middle,
top: P.extraSmall,
bottom: P.extraSmall),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
r.idFormatted,
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.w600,
height: 1,
letterSpacing: -1,
fontFeatures: [FontFeature.tabularFigures()]), // 数字等宽
),
const SizedBox(width: P.small),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ExtendedText(r.title,
specialTextSpanBuilder: highlightTextBuilder,
style: const TextStyle(fontSize: 20)),
ExtendedText(r.content,
specialTextSpanBuilder: highlightTextBuilder),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text("${r.insertDate}",
textAlign: TextAlign.end,
style: const TextStyle(
fontSize: 12, color: Colors.grey)),
],
)
],
),
),
],
),
),
),
);
},
);
}
/// 对话框
AlertDialog buildDialog(BuildContext context, MainTableRowUiModel r) {
final colorScheme = Theme.of(context).colorScheme;
return AlertDialog(
insetPadding: const EdgeInsets.all(0),
contentPadding: const EdgeInsets.all(0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0)),
content: Container(
width: MediaQuery.of(context).size.width,
padding: const EdgeInsets.all(P.middle),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
r.idFormatted,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
height: 1,
letterSpacing: -1,
fontFeatures: [FontFeature.tabularFigures()]),
),
const SizedBox(height: P.small),
ExtendedText(
r.title,
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w600),
textAlign: TextAlign.center,
specialTextSpanBuilder: highlightTextBuilder,
),
const SizedBox(height: P.extraSmall),
ExtendedText(
r.content,
specialTextSpanBuilder: highlightTextBuilder,
style: const TextStyle(fontSize: 18, height: 1.3),
),
const SizedBox(height: P.middle),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(P.small),
),
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
child: const Text("确定"),
)
],
),
),
);
}
final highlightTextBuilder =
HighlightTextSpanBuilder((src) => src.copyWith(color: Colors.red));
}
enum DbImplement { sqlite3, sqfliteCommonFfi }
class P {
static const middle = 16.0;
static const small = 8.0;
static const extraSmall = 4.0;
}