sqlite3_simple 1.0.6 copy "sqlite3_simple: ^1.0.6" to clipboard
sqlite3_simple: ^1.0.6 copied to clipboard

基于 Simple (支持中文和拼音的 SQLite fts5 全文搜索扩展) 和 sqlite3.dart 的 Flutter 库,用于 SQLite 中文和拼音全文搜索。

example/lib/main.dart

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;
}
0
likes
140
points
157
downloads

Publisher

unverified uploader

Weekly Downloads

基于 Simple (支持中文和拼音的 SQLite fts5 全文搜索扩展) 和 sqlite3.dart 的 Flutter 库,用于 SQLite 中文和拼音全文搜索。

Homepage
Repository (GitHub)

Documentation

API reference

License

MIT (license)

Dependencies

flutter, path, plugin_platform_interface, sqlite3

More

Packages that depend on sqlite3_simple

Packages that implement sqlite3_simple