import 'package:flutter/material.dart'; import 'package:miaoji_zhang/shared/api/api_client.dart'; import 'package:miaoji_zhang/shared/api/business_api.dart'; import 'package:miaoji_zhang/shared/theme/app_theme.dart'; import 'package:miaoji_zhang/shared/widgets/category_icon.dart'; import 'package:miaoji_zhang/shared/widgets/async_error_view.dart'; import 'package:miaoji_zhang/shared/services/shanghai_time.dart'; /// 搜索页(P19) class SearchPage extends StatefulWidget { const SearchPage({super.key}); @override State createState() => _SearchPageState(); } class _SearchPageState extends State { final _input = TextEditingController(); List _results = []; bool _aiOnly = false; bool _searched = false; bool _loading = false; String? _error; String? _timeFilter; // today | week | month double? _minAmount, _maxAmount; Future _search() async { final q = _input.text.trim(); if (q.isEmpty && !_aiOnly && _timeFilter == null && _minAmount == null && _maxAmount == null) return; setState(() { _loading = true; _error = null; }); try { final now = ShanghaiTime.now; DateTime? from; DateTime? to; if (_timeFilter == 'today') { from = DateTime(now.year, now.month, now.day); to = from.add(const Duration(days: 1)); } else if (_timeFilter == 'week') { final today = DateTime(now.year, now.month, now.day); from = today.subtract(Duration(days: today.weekday - 1)); to = from.add(const Duration(days: 7)); } else if (_timeFilter == 'month') { from = DateTime(now.year, now.month); to = DateTime(now.year, now.month + 1); } final list = await SearchApi.search( q: q, aiOnly: _aiOnly, minAmount: _minAmount, maxAmount: _maxAmount, from: from, to: to, ); if (mounted) setState(() { _results = list; _searched = true; }); } catch (error) { if (mounted) setState(() => _error = apiErrorMessage(error)); } finally { if (mounted) setState(() => _loading = false); } } @override Widget build(BuildContext context) { final total = _results .where((t) => !t.isIncome) .fold(0, (s, t) => s + t.amount); return Scaffold( appBar: AppBar( title: TextField( controller: _input, autofocus: true, textInputAction: TextInputAction.search, onSubmitted: (_) => _search(), decoration: InputDecoration( hintText: '搜备注 / 分类 / 你说过的话', contentPadding: EdgeInsets.symmetric(horizontal: 14, vertical: 8), ), ), actions: [TextButton(onPressed: _search, child: Text('搜索'))], ), body: Column( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Wrap( spacing: 6, runSpacing: 6, children: [ FilterChip( label: Text('仅 AI 记账', style: TextStyle(fontSize: 11)), selected: _aiOnly, selectedColor: context.jz.aiBackground, checkmarkColor: AppTheme.ai, onSelected: (v) { setState(() => _aiOnly = v); _search(); }, ), ...['today', 'week', 'month'].map( (t) => FilterChip( label: Text( {'today': '今天', 'week': '本周', 'month': '本月'}[t]!, style: TextStyle(fontSize: 11), ), selected: _timeFilter == t, selectedColor: context.jz.primaryBackground, onSelected: (v) { setState(() => _timeFilter = v ? t : null); _search(); }, ), ), if (_searched && !_loading) Padding( padding: const EdgeInsets.symmetric(horizontal: 6), child: Text( '${_results.length} 笔 · ¥${total.toStringAsFixed(2)}', style: TextStyle(fontSize: 11, color: context.jz.text3), ), ), ], ), ), Expanded( child: _loading ? Center( child: CircularProgressIndicator( color: AppTheme.primary, strokeWidth: 2, ), ) : _error != null ? AsyncErrorView(message: _error!, onRetry: _search) : !_searched ? Center( child: Text( '输入关键词搜账单\n也能搜到你和 AI 说过的原话', textAlign: TextAlign.center, style: TextStyle( fontSize: 12, color: context.jz.text3, height: 1.8, ), ), ) : _results.isEmpty ? Center( child: Text( '没有找到相关账单', style: TextStyle(fontSize: 12, color: context.jz.text3), ), ) : ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: _results.length, itemBuilder: (ctx, i) { final t = _results[i]; return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.symmetric( horizontal: 13, vertical: 10, ), decoration: BoxDecoration( color: context.jz.card, borderRadius: BorderRadius.circular(12), border: Border.all( color: context.jz.line, width: 0.5, ), ), child: Row( children: [ CategoryIconBox( iconKey: t.categoryIcon, colorKey: t.categoryColor, ), SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( t.note ?? t.categoryName, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, ), ), SizedBox(height: 2), Row( children: [ Text( '${t.occurredAt.month}月${t.occurredAt.day}日', style: TextStyle( fontSize: 10.5, color: context.jz.text3, ), ), if (t.isAi) ...[ SizedBox(width: 5), Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 1.5, ), decoration: BoxDecoration( color: context.jz.aiBackground, borderRadius: BorderRadius.circular( 6, ), ), child: Text( '✦ AI', style: TextStyle( fontSize: 9, color: AppTheme.ai, fontWeight: FontWeight.w600, ), ), ), ], ], ), ], ), ), Text( '${t.isIncome ? '+' : '-'}${t.amount.toStringAsFixed(2)}', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: t.isIncome ? AppTheme.primary : context.jz.text, ), ), ], ), ); }, ), ), ], ), ); } }