chore: improve rendering of vocab analytics popup, exclude empty lemmas, enable filtering

This commit is contained in:
ggurdin 2025-03-17 16:09:26 -04:00 committed by GitHub
parent dc1b7f1f5a
commit 6d393f7745
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 212 additions and 47 deletions

View file

@ -34,8 +34,8 @@ class AnalyticsDetailsViewContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
final Color textColor = (Theme.of(context).brightness != Brightness.light
? construct.lemmaCategory.color
: construct.lemmaCategory.darkColor) as Color;
? construct.lemmaCategory.color(context)
: construct.lemmaCategory.darkColor(context));
return SingleChildScrollView(
child: Column(

View file

@ -55,8 +55,8 @@ class LemmaUsageDots extends StatelessWidget {
}
final Color textColor = (Theme.of(context).brightness != Brightness.light
? construct.lemmaCategory.color
: construct.lemmaCategory.darkColor) as Color;
? construct.lemmaCategory.color(context)
: construct.lemmaCategory.darkColor(context));
return Padding(
padding: const EdgeInsets.symmetric(vertical: 20),

View file

@ -69,8 +69,8 @@ class VocabDetailsView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final Color textColor = (Theme.of(context).brightness != Brightness.light
? _construct.lemmaCategory.color
: _construct.lemmaCategory.darkColor) as Color;
? _construct.lemmaCategory.color(context)
: _construct.lemmaCategory.darkColor(context));
return AnalyticsDetailsViewContent(
title: Row(

View file

@ -39,7 +39,9 @@ class VocabAnalyticsListTileState extends State<VocabAnalyticsListTile> {
padding: EdgeInsets.all(padding),
decoration: BoxDecoration(
color: _isHovered
? widget.constructUse.constructLevel.color(context)
? widget.constructUse.constructLevel
.color(context)
.withAlpha(20)
: Colors.transparent,
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
),
@ -59,7 +61,7 @@ class VocabAnalyticsListTileState extends State<VocabAnalyticsListTile> {
fontSize: 22,
),
)
: widget.constructUse.constructLevel.icon(10),
: widget.constructUse.constructLevel.icon(36.0),
),
),
Container(

View file

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:fluffychat/pangea/analytics_details_popup/vocab_analytics_list_tile.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
@ -11,59 +13,220 @@ import 'package:fluffychat/widgets/matrix.dart';
/// Displays vocab analytics, sorted into categories
/// (flowers, greens, and seeds) by points
class VocabAnalyticsListView extends StatelessWidget {
class VocabAnalyticsListView extends StatefulWidget {
final void Function(ConstructIdentifier) onConstructZoom;
List<ConstructUses> get vocab => MatrixState
.pangeaController.getAnalytics.constructListModel
.constructList(type: ConstructTypeEnum.vocab)
..sort((a, b) => a.lemma.toLowerCase().compareTo(b.lemma.toLowerCase()));
const VocabAnalyticsListView({
super.key,
required this.onConstructZoom,
});
@override
VocabAnalyticsListViewState createState() => VocabAnalyticsListViewState();
}
class VocabAnalyticsListViewState extends State<VocabAnalyticsListView> {
bool _isSearching = false;
final TextEditingController _searchController = TextEditingController();
ConstructLevelEnum? _selectedConstructLevel;
@override
void initState() {
super.initState();
_searchController.addListener(() {
if (mounted) setState(() {});
});
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
void _setSelectedConstructLevel(ConstructLevelEnum level) {
setState(() {
_selectedConstructLevel = _selectedConstructLevel == level ? null : level;
});
}
void _toggleSearching() {
setState(() {
_isSearching = !_isSearching;
_selectedConstructLevel = null;
_searchController.clear();
});
}
List<ConstructUses> get _vocab => MatrixState
.pangeaController.getAnalytics.constructListModel
.constructList(type: ConstructTypeEnum.vocab)
.sorted((a, b) => a.lemma.toLowerCase().compareTo(b.lemma.toLowerCase()));
List<ConstructUses> get _filteredVocab => _vocab
.where(
(use) =>
use.lemma.isNotEmpty &&
(_selectedConstructLevel == null
? true
: use.lemmaCategory == _selectedConstructLevel) &&
(_isSearching
? use.lemma
.toLowerCase()
.contains(_searchController.text.toLowerCase())
: true),
)
.toList();
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: [
const InstructionsInlineTooltip(
instructionsEnum: InstructionsEnum.analyticsVocabList,
),
Padding(
padding: const EdgeInsets.all(16.0),
final List<Widget> filters = ConstructLevelEnum.values.reversed
.map((constructLevelCategory) {
final int count = _vocab
.where((e) => e.lemmaCategory == constructLevelCategory)
.length;
return InkWell(
onTap: () => _setSelectedConstructLevel(constructLevelCategory),
customBorder: const CircleBorder(),
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _selectedConstructLevel == constructLevelCategory
? constructLevelCategory.color(context).withAlpha(50)
: null,
),
padding: const EdgeInsets.all(8.0),
child: Badge(
label: Text(count.toString()),
child: constructLevelCategory.icon(24),
),
),
);
})
.cast<Widget>()
.toList();
filters.add(
IconButton(
icon: const Icon(Icons.search_outlined),
onPressed: _toggleSearching,
),
);
return Column(
children: [
const InstructionsInlineTooltip(
instructionsEnum: InstructionsEnum.analyticsVocabList,
),
Padding(
padding: const EdgeInsets.all(32.0),
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
padding:
EdgeInsets.symmetric(horizontal: _isSearching ? 8.0 : 24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 50,
children: ConstructLevelEnum.values.reversed
.map((constructLevelCategory) {
final int count = vocab
.where((e) => e.lemmaCategory == constructLevelCategory)
.length;
return Badge(
label: Text(count.toString()),
child: constructLevelCategory.icon(24),
);
}).toList(),
children: [
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 225.0),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) => FadeTransition(
opacity: animation,
child: child,
),
child: _isSearching
? Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
key: const ValueKey('search'),
children: [
Expanded(
child: TextField(
autofocus: true,
controller: _searchController,
decoration: const InputDecoration(
contentPadding: EdgeInsets.symmetric(
vertical: 6.0,
horizontal: 12.0,
),
isDense: true,
border: OutlineInputBorder(),
),
),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: _toggleSearching,
),
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
key: const ValueKey('filters'),
children: filters,
),
),
),
],
),
),
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
runAlignment: WrapAlignment.start,
children: vocab
.map(
(vocab) => VocabAnalyticsListTile(
onTap: () => onConstructZoom(vocab.id),
constructUse: vocab,
),
)
.toList(),
),
// Padding(
// padding: const EdgeInsets.all(32.0),
// child: Row(
// spacing: _isSearching ? 8.0 : 24.0,
// mainAxisAlignment: MainAxisAlignment.center,
// children: _isSearching
// ? [
// ConstrainedBox(
// constraints: const BoxConstraints(maxWidth: 200),
// child: TextField(
// autofocus: true,
// controller: _searchController,
// decoration: const InputDecoration(
// contentPadding: EdgeInsets.symmetric(
// vertical: 6.0,
// horizontal: 12.0,
// ),
// isDense: true,
// border: OutlineInputBorder(),
// ),
// onChanged: (value) {
// if (mounted) setState(() {});
// },
// ),
// ),
// IconButton(
// icon: const Icon(Icons.close),
// onPressed: _toggleSearching,
// ),
// ]
// : filters,
// ),
// ),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 100.0,
mainAxisExtent: 100.0,
crossAxisSpacing: 8.0,
mainAxisSpacing: 8.0,
),
itemCount: _filteredVocab.length,
itemBuilder: (context, index) {
final vocabItem = _filteredVocab[index];
return VocabAnalyticsListTile(
onTap: () => widget.onConstructZoom(vocabItem.id),
constructUse: vocabItem,
);
},
),
),
],
),
),
],
);
}
}