|
@@ -1,6 +1,5 @@
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
-import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
|
import '../../services/api_service.dart';
|
|
import '../../services/api_service.dart';
|
|
|
import '../../providers/app_provider.dart';
|
|
import '../../providers/app_provider.dart';
|
|
|
import '../../models/problem.dart';
|
|
import '../../models/problem.dart';
|
|
@@ -18,87 +17,107 @@ class HomePage extends ConsumerStatefulWidget {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class _HomePageState extends ConsumerState<HomePage> {
|
|
class _HomePageState extends ConsumerState<HomePage> {
|
|
|
- String? _selectedChapter;
|
|
|
|
|
|
|
+ String? _filterCode;
|
|
|
String? _selectedType;
|
|
String? _selectedType;
|
|
|
|
|
+ int? _viewedCartId;
|
|
|
List<Problem> _problems = [];
|
|
List<Problem> _problems = [];
|
|
|
Problem? _selectedProblem;
|
|
Problem? _selectedProblem;
|
|
|
- int _page = 1;
|
|
|
|
|
- int _total = 0;
|
|
|
|
|
bool _isLoading = false;
|
|
bool _isLoading = false;
|
|
|
- int? _lastSyncedCartId;
|
|
|
|
|
- final _scrollController = ScrollController();
|
|
|
|
|
|
|
+ Set<String> _cartIds = {};
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
|
void initState() {
|
|
void initState() {
|
|
|
super.initState();
|
|
super.initState();
|
|
|
- WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
|
|
|
- final prefs = await SharedPreferences.getInstance();
|
|
|
|
|
- final savedId = prefs.getInt('active_cart_id');
|
|
|
|
|
- if (savedId != null && ref.read(activeCartIdProvider) == null) {
|
|
|
|
|
- ref.read(activeCartIdProvider.notifier).state = savedId;
|
|
|
|
|
- await _syncCartItems(savedId);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
|
|
|
+ _loadCart();
|
|
|
_loadProblems();
|
|
_loadProblems();
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- Future<void> _syncCartItems(int cartId) async {
|
|
|
|
|
|
|
+ Future<void> _loadCart() async {
|
|
|
final api = ref.read(apiServiceProvider);
|
|
final api = ref.read(apiServiceProvider);
|
|
|
try {
|
|
try {
|
|
|
- final data = await api.cartDetail(cartId);
|
|
|
|
|
- final items = data['items'] as List? ?? [];
|
|
|
|
|
- ref.read(cartProblemIdsProvider.notifier).state =
|
|
|
|
|
- items.map((i) => i['id'] as String).toSet();
|
|
|
|
|
|
|
+ final carts = await api.listCarts();
|
|
|
|
|
+ final list = carts['carts'] as List? ?? [];
|
|
|
|
|
+ if (list.isEmpty) {
|
|
|
|
|
+ // Create default cart
|
|
|
|
|
+ await api.createCart('默认');
|
|
|
|
|
+ _viewedCartId = null;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ _viewedCartId = list.first['id'] as int;
|
|
|
|
|
+ }
|
|
|
|
|
+ // Load cart items
|
|
|
|
|
+ if (_viewedCartId != null) {
|
|
|
|
|
+ final cart = await api.cartDetail(_viewedCartId!);
|
|
|
|
|
+ final items = cart['items'] as List? ?? [];
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _cartIds = items.map((i) => i['id'] as String).toSet();
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
} catch (_) {}
|
|
} catch (_) {}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- @override
|
|
|
|
|
- void dispose() {
|
|
|
|
|
- _scrollController.dispose();
|
|
|
|
|
- super.dispose();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Future<void> _loadProblems({bool refresh = false}) async {
|
|
|
|
|
|
|
+ Future<void> _loadProblems() async {
|
|
|
if (_isLoading) return;
|
|
if (_isLoading) return;
|
|
|
setState(() => _isLoading = true);
|
|
setState(() => _isLoading = true);
|
|
|
-
|
|
|
|
|
- if (refresh) _page = 1;
|
|
|
|
|
-
|
|
|
|
|
final api = ref.read(apiServiceProvider);
|
|
final api = ref.read(apiServiceProvider);
|
|
|
try {
|
|
try {
|
|
|
final data = await api.listProblems(
|
|
final data = await api.listProblems(
|
|
|
- chapter: _selectedChapter,
|
|
|
|
|
|
|
+ knowledgeCode: _filterCode,
|
|
|
questionType: _selectedType,
|
|
questionType: _selectedType,
|
|
|
- page: _page,
|
|
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ pageSize: 10000, // All problems, no pagination
|
|
|
);
|
|
);
|
|
|
- final items = (data['items'] as List).map((j) => Problem.fromJson(j)).toList();
|
|
|
|
|
setState(() {
|
|
setState(() {
|
|
|
- if (refresh || _page == 1) {
|
|
|
|
|
- _problems = items;
|
|
|
|
|
- } else {
|
|
|
|
|
- _problems.addAll(items);
|
|
|
|
|
- }
|
|
|
|
|
- _total = data['total'] ?? 0;
|
|
|
|
|
|
|
+ _problems = (data['items'] as List).map((j) => Problem.fromJson(j)).toList();
|
|
|
});
|
|
});
|
|
|
- } catch (_) {
|
|
|
|
|
- // Error handled gracefully
|
|
|
|
|
- } finally {
|
|
|
|
|
- setState(() => _isLoading = false);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ } catch (_) {}
|
|
|
|
|
+ setState(() => _isLoading = false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- void _selectChapter(String? chapterFilter, String? displayName) {
|
|
|
|
|
- setState(() => _selectedChapter = chapterFilter);
|
|
|
|
|
- _loadProblems(refresh: true);
|
|
|
|
|
|
|
+ void _selectNode(String? code, String? name) {
|
|
|
|
|
+ setState(() => _filterCode = code);
|
|
|
|
|
+ _loadProblems();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void _selectProblem(Problem p) {
|
|
void _selectProblem(Problem p) {
|
|
|
setState(() => _selectedProblem = p);
|
|
setState(() => _selectedProblem = p);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ Future<void> _addToCart(String problemId) async {
|
|
|
|
|
+ final api = ref.read(apiServiceProvider);
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (_viewedCartId == null) return;
|
|
|
|
|
+ await api.addToCart(_viewedCartId!, problemId);
|
|
|
|
|
+ final cart = await api.cartDetail(_viewedCartId!);
|
|
|
|
|
+ final items = cart['items'] as List? ?? [];
|
|
|
|
|
+ setState(() => _cartIds = items.map((i) => i['id'] as String).toSet());
|
|
|
|
|
+ if (mounted) {
|
|
|
|
|
+ ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
+ const SnackBar(content: Text('已加入试题篮'), duration: Duration(seconds: 1)),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (_) {}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Future<void> _removeFromCart(String problemId) async {
|
|
|
|
|
+ if (_viewedCartId == null) return;
|
|
|
|
|
+ final api = ref.read(apiServiceProvider);
|
|
|
|
|
+ try {
|
|
|
|
|
+ await api.removeFromCart(_viewedCartId!, problemId);
|
|
|
|
|
+ final cart = await api.cartDetail(_viewedCartId!);
|
|
|
|
|
+ final items = cart['items'] as List? ?? [];
|
|
|
|
|
+ setState(() => _cartIds = items.map((i) => i['id'] as String).toSet());
|
|
|
|
|
+ if (mounted) {
|
|
|
|
|
+ ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
+ const SnackBar(content: Text('已从试题篮移除'), duration: Duration(seconds: 1)),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (_) {}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
@override
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
Widget build(BuildContext context) {
|
|
|
- // Responsive: wide = three columns, narrow = single column
|
|
|
|
|
final isWide = MediaQuery.of(context).size.width > 900;
|
|
final isWide = MediaQuery.of(context).size.width > 900;
|
|
|
|
|
|
|
|
return Scaffold(
|
|
return Scaffold(
|
|
@@ -110,73 +129,36 @@ class _HomePageState extends ConsumerState<HomePage> {
|
|
|
tooltip: '导入题目',
|
|
tooltip: '导入题目',
|
|
|
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ImportPage())),
|
|
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ImportPage())),
|
|
|
),
|
|
),
|
|
|
- if (!isWide && _selectedProblem != null)
|
|
|
|
|
- IconButton(
|
|
|
|
|
- icon: const Icon(Icons.visibility),
|
|
|
|
|
- tooltip: '查看题目',
|
|
|
|
|
- onPressed: () => Navigator.push(
|
|
|
|
|
- context,
|
|
|
|
|
- MaterialPageRoute(
|
|
|
|
|
- builder: (_) => ProblemDetailPage(problemId: _selectedProblem!.id),
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
],
|
|
],
|
|
|
),
|
|
),
|
|
|
- body: isWide ? _buildWideLayout() : _buildNarrowLayout(),
|
|
|
|
|
|
|
+ body: isWide ? _buildWide() : _buildNarrow(),
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- Widget _buildWideLayout() {
|
|
|
|
|
|
|
+ Widget _buildWide() {
|
|
|
return Row(
|
|
return Row(
|
|
|
children: [
|
|
children: [
|
|
|
- // Left: Knowledge tree
|
|
|
|
|
SizedBox(
|
|
SizedBox(
|
|
|
width: 280,
|
|
width: 280,
|
|
|
- child: KnowledgeTreeWidget(
|
|
|
|
|
- onSelectChapter: _selectChapter,
|
|
|
|
|
- ),
|
|
|
|
|
|
|
+ child: KnowledgeTreeWidget(onSelectNode: _selectNode),
|
|
|
),
|
|
),
|
|
|
const VerticalDivider(width: 1),
|
|
const VerticalDivider(width: 1),
|
|
|
-
|
|
|
|
|
- // Middle: Problem list
|
|
|
|
|
- Expanded(
|
|
|
|
|
- flex: 2,
|
|
|
|
|
- child: _buildProblemList(),
|
|
|
|
|
- ),
|
|
|
|
|
-
|
|
|
|
|
- // Right: Preview
|
|
|
|
|
|
|
+ Expanded(flex: 2, child: _buildList()),
|
|
|
Expanded(
|
|
Expanded(
|
|
|
flex: 3,
|
|
flex: 3,
|
|
|
child: _selectedProblem != null
|
|
child: _selectedProblem != null
|
|
|
- ? _buildPreview()
|
|
|
|
|
|
|
+ ? ProblemPreview(problem: _selectedProblem!)
|
|
|
: const Center(child: Text('点击题目查看预览')),
|
|
: const Center(child: Text('点击题目查看预览')),
|
|
|
),
|
|
),
|
|
|
],
|
|
],
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- Widget _buildNarrowLayout() {
|
|
|
|
|
- return _buildProblemList();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Widget _buildProblemList() {
|
|
|
|
|
- final activeCartId = ref.watch(activeCartIdProvider);
|
|
|
|
|
- final cartIds = ref.watch(cartProblemIdsProvider);
|
|
|
|
|
-
|
|
|
|
|
- // Detect active cart change and sync (only once per change, via flag)
|
|
|
|
|
- if (activeCartId != _lastSyncedCartId) {
|
|
|
|
|
- _lastSyncedCartId = activeCartId;
|
|
|
|
|
- if (activeCartId != null) {
|
|
|
|
|
- _syncCartItems(activeCartId);
|
|
|
|
|
- } else {
|
|
|
|
|
- ref.read(cartProblemIdsProvider.notifier).state = {};
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ Widget _buildNarrow() => _buildList();
|
|
|
|
|
|
|
|
|
|
+ Widget _buildList() {
|
|
|
return Column(
|
|
return Column(
|
|
|
children: [
|
|
children: [
|
|
|
- // Filter bar
|
|
|
|
|
Padding(
|
|
Padding(
|
|
|
padding: const EdgeInsets.all(8),
|
|
padding: const EdgeInsets.all(8),
|
|
|
child: Row(
|
|
child: Row(
|
|
@@ -188,9 +170,10 @@ class _HomePageState extends ConsumerState<HomePage> {
|
|
|
labelText: '题型',
|
|
labelText: '题型',
|
|
|
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
|
border: OutlineInputBorder(),
|
|
border: OutlineInputBorder(),
|
|
|
|
|
+ isDense: true,
|
|
|
),
|
|
),
|
|
|
items: const [
|
|
items: const [
|
|
|
- DropdownMenuItem(value: null, child: Text('全部')),
|
|
|
|
|
|
|
+ DropdownMenuItem(value: null, child: Text('全部题型')),
|
|
|
DropdownMenuItem(value: '单选题', child: Text('单选题')),
|
|
DropdownMenuItem(value: '单选题', child: Text('单选题')),
|
|
|
DropdownMenuItem(value: '多选题', child: Text('多选题')),
|
|
DropdownMenuItem(value: '多选题', child: Text('多选题')),
|
|
|
DropdownMenuItem(value: '填空题', child: Text('填空题')),
|
|
DropdownMenuItem(value: '填空题', child: Text('填空题')),
|
|
@@ -198,197 +181,44 @@ class _HomePageState extends ConsumerState<HomePage> {
|
|
|
],
|
|
],
|
|
|
onChanged: (v) {
|
|
onChanged: (v) {
|
|
|
setState(() => _selectedType = v);
|
|
setState(() => _selectedType = v);
|
|
|
- _loadProblems(refresh: true);
|
|
|
|
|
|
|
+ _loadProblems();
|
|
|
},
|
|
},
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
const SizedBox(width: 8),
|
|
const SizedBox(width: 8),
|
|
|
- Text('共 $_total 题', style: Theme.of(context).textTheme.bodySmall),
|
|
|
|
|
|
|
+ Text('共 ${_problems.length} 题', style: Theme.of(context).textTheme.bodySmall),
|
|
|
],
|
|
],
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
-
|
|
|
|
|
- // List
|
|
|
|
|
Expanded(
|
|
Expanded(
|
|
|
child: _isLoading && _problems.isEmpty
|
|
child: _isLoading && _problems.isEmpty
|
|
|
? const Center(child: CircularProgressIndicator())
|
|
? const Center(child: CircularProgressIndicator())
|
|
|
: _problems.isEmpty
|
|
: _problems.isEmpty
|
|
|
- ? const Center(child: Text('没有题目,请先导入'))
|
|
|
|
|
|
|
+ ? const Center(child: Text('没有题目'))
|
|
|
: ListView.builder(
|
|
: ListView.builder(
|
|
|
- controller: _scrollController,
|
|
|
|
|
itemCount: _problems.length,
|
|
itemCount: _problems.length,
|
|
|
- itemBuilder: (_, i) => ProblemCard(
|
|
|
|
|
- problem: _problems[i],
|
|
|
|
|
- isSelected: _selectedProblem?.id == _problems[i].id,
|
|
|
|
|
- isInCart: activeCartId != null && cartIds.contains(_problems[i].id),
|
|
|
|
|
- onTap: () {
|
|
|
|
|
- _selectProblem(_problems[i]);
|
|
|
|
|
- final isWide = MediaQuery.of(context).size.width > 900;
|
|
|
|
|
- if (!isWide) {
|
|
|
|
|
- Navigator.push(
|
|
|
|
|
- context,
|
|
|
|
|
- MaterialPageRoute(
|
|
|
|
|
- builder: (_) => ProblemDetailPage(problemId: _problems[i].id),
|
|
|
|
|
- ),
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- onAddToCart: (pid) => _addToCart(pid),
|
|
|
|
|
- onRemoveFromCart: (pid) => _removeFromCart(pid),
|
|
|
|
|
- ),
|
|
|
|
|
|
|
+ itemBuilder: (_, i) {
|
|
|
|
|
+ final p = _problems[i];
|
|
|
|
|
+ return ProblemCard(
|
|
|
|
|
+ problem: p,
|
|
|
|
|
+ isSelected: _selectedProblem?.id == p.id,
|
|
|
|
|
+ isInCart: _cartIds.contains(p.id),
|
|
|
|
|
+ onTap: () {
|
|
|
|
|
+ _selectProblem(p);
|
|
|
|
|
+ if (!(MediaQuery.of(context).size.width > 900)) {
|
|
|
|
|
+ Navigator.push(
|
|
|
|
|
+ context,
|
|
|
|
|
+ MaterialPageRoute(builder: (_) => ProblemDetailPage(problemId: p.id)),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ onAddToCart: _addToCart,
|
|
|
|
|
+ onRemoveFromCart: _removeFromCart,
|
|
|
|
|
+ );
|
|
|
|
|
+ },
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
],
|
|
],
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- Widget _buildPreview() {
|
|
|
|
|
- if (_selectedProblem == null) return const SizedBox.shrink();
|
|
|
|
|
- return ProblemPreview(problem: _selectedProblem!);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Future<void> _addToCart(String problemId) async {
|
|
|
|
|
- final api = ref.read(apiServiceProvider);
|
|
|
|
|
- try {
|
|
|
|
|
- final prefs = await SharedPreferences.getInstance();
|
|
|
|
|
- int? activeCartId = ref.read(activeCartIdProvider);
|
|
|
|
|
-
|
|
|
|
|
- if (activeCartId == null) {
|
|
|
|
|
- final cartsData = await api.listCarts();
|
|
|
|
|
- final carts = cartsData['carts'] as List? ?? [];
|
|
|
|
|
- if (!mounted) return;
|
|
|
|
|
-
|
|
|
|
|
- if (carts.isEmpty) {
|
|
|
|
|
- final result = await api.createCart('默认');
|
|
|
|
|
- activeCartId = result['id'] as int;
|
|
|
|
|
- } else {
|
|
|
|
|
- activeCartId = await showDialog<int>(
|
|
|
|
|
- context: context,
|
|
|
|
|
- builder: (ctx) => _CartPickerDialog(carts: carts, api: api),
|
|
|
|
|
- );
|
|
|
|
|
- if (activeCartId == null || !mounted) return;
|
|
|
|
|
- }
|
|
|
|
|
- await prefs.setInt('active_cart_id', activeCartId!);
|
|
|
|
|
- ref.read(activeCartIdProvider.notifier).state = activeCartId;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- await api.addToCart(activeCartId, problemId);
|
|
|
|
|
- ref.read(cartProblemIdsProvider.notifier).state = {
|
|
|
|
|
- ...ref.read(cartProblemIdsProvider),
|
|
|
|
|
- problemId,
|
|
|
|
|
- };
|
|
|
|
|
- if (mounted) {
|
|
|
|
|
- ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
- const SnackBar(content: Text('已加入试题篮'), duration: Duration(seconds: 1)),
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- } catch (_) {}
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Future<void> _removeFromCart(String problemId) async {
|
|
|
|
|
- final activeCartId = ref.read(activeCartIdProvider);
|
|
|
|
|
- if (activeCartId == null) return;
|
|
|
|
|
- final api = ref.read(apiServiceProvider);
|
|
|
|
|
- try {
|
|
|
|
|
- await api.removeFromCart(activeCartId, problemId);
|
|
|
|
|
- final updated = {...ref.read(cartProblemIdsProvider)};
|
|
|
|
|
- updated.remove(problemId);
|
|
|
|
|
- ref.read(cartProblemIdsProvider.notifier).state = updated;
|
|
|
|
|
- if (mounted) {
|
|
|
|
|
- ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
- const SnackBar(content: Text('已从试题篮移除'), duration: Duration(seconds: 1)),
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- } catch (_) {}
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/// 试题篮选择对话框
|
|
|
|
|
-class _CartPickerDialog extends StatefulWidget {
|
|
|
|
|
- final List carts;
|
|
|
|
|
- final dynamic api;
|
|
|
|
|
-
|
|
|
|
|
- const _CartPickerDialog({required this.carts, required this.api});
|
|
|
|
|
-
|
|
|
|
|
- @override
|
|
|
|
|
- State<_CartPickerDialog> createState() => _CartPickerDialogState();
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-class _CartPickerDialogState extends State<_CartPickerDialog> {
|
|
|
|
|
- final _newNameController = TextEditingController();
|
|
|
|
|
-
|
|
|
|
|
- @override
|
|
|
|
|
- void dispose() {
|
|
|
|
|
- _newNameController.dispose();
|
|
|
|
|
- super.dispose();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Future<void> _createAndSelect() async {
|
|
|
|
|
- final name = _newNameController.text.trim();
|
|
|
|
|
- if (name.isEmpty) return;
|
|
|
|
|
-
|
|
|
|
|
- final result = await widget.api.createCart(name);
|
|
|
|
|
- final newId = result['id'] as int;
|
|
|
|
|
- if (mounted) Navigator.pop(context, newId);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @override
|
|
|
|
|
- Widget build(BuildContext context) {
|
|
|
|
|
- return AlertDialog(
|
|
|
|
|
- title: const Text('选择试题篮'),
|
|
|
|
|
- content: SizedBox(
|
|
|
|
|
- width: 300,
|
|
|
|
|
- child: Column(
|
|
|
|
|
- mainAxisSize: MainAxisSize.min,
|
|
|
|
|
- crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
|
|
|
- children: [
|
|
|
|
|
- if (widget.carts.isEmpty)
|
|
|
|
|
- const Padding(
|
|
|
|
|
- padding: EdgeInsets.only(bottom: 16),
|
|
|
|
|
- child: Text('暂无试题篮,请创建:', style: TextStyle(color: Colors.grey)),
|
|
|
|
|
- ),
|
|
|
|
|
-
|
|
|
|
|
- // Existing carts
|
|
|
|
|
- ...widget.carts.map((cart) => ListTile(
|
|
|
|
|
- leading: const Icon(Icons.folder_outlined),
|
|
|
|
|
- title: Text(cart['name'] ?? ''),
|
|
|
|
|
- subtitle: Text('${cart['item_count'] ?? 0} 题'),
|
|
|
|
|
- onTap: () => Navigator.pop(context, cart['id'] as int),
|
|
|
|
|
- )),
|
|
|
|
|
-
|
|
|
|
|
- const Divider(),
|
|
|
|
|
-
|
|
|
|
|
- // Create new cart
|
|
|
|
|
- Row(
|
|
|
|
|
- children: [
|
|
|
|
|
- Expanded(
|
|
|
|
|
- child: TextField(
|
|
|
|
|
- controller: _newNameController,
|
|
|
|
|
- decoration: const InputDecoration(
|
|
|
|
|
- hintText: '新建篮子名称',
|
|
|
|
|
- border: OutlineInputBorder(),
|
|
|
|
|
- isDense: true,
|
|
|
|
|
- ),
|
|
|
|
|
- onSubmitted: (_) => _createAndSelect(),
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- const SizedBox(width: 8),
|
|
|
|
|
- IconButton(
|
|
|
|
|
- icon: const Icon(Icons.add_circle),
|
|
|
|
|
- tooltip: '创建并选择',
|
|
|
|
|
- onPressed: _createAndSelect,
|
|
|
|
|
- ),
|
|
|
|
|
- ],
|
|
|
|
|
- ),
|
|
|
|
|
- ],
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- actions: [
|
|
|
|
|
- TextButton(
|
|
|
|
|
- onPressed: () => Navigator.pop(context),
|
|
|
|
|
- child: const Text('取消'),
|
|
|
|
|
- ),
|
|
|
|
|
- ],
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|