diff --git a/lib/pangea/analytics_details_popup/analytics_details_popup_content.dart b/lib/pangea/analytics_details_popup/analytics_details_popup_content.dart index e334ffb85..0bf03395a 100644 --- a/lib/pangea/analytics_details_popup/analytics_details_popup_content.dart +++ b/lib/pangea/analytics_details_popup/analytics_details_popup_content.dart @@ -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( diff --git a/lib/pangea/analytics_details_popup/lemma_usage_dots.dart b/lib/pangea/analytics_details_popup/lemma_usage_dots.dart index 80616008b..817fe763e 100644 --- a/lib/pangea/analytics_details_popup/lemma_usage_dots.dart +++ b/lib/pangea/analytics_details_popup/lemma_usage_dots.dart @@ -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), diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart index 2d81d28fa..9056e5a73 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart @@ -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( diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_list_tile.dart b/lib/pangea/analytics_details_popup/vocab_analytics_list_tile.dart index 8bed29344..383db123a 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_list_tile.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_list_tile.dart @@ -39,7 +39,9 @@ class VocabAnalyticsListTileState extends State { 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 { fontSize: 22, ), ) - : widget.constructUse.constructLevel.icon(10), + : widget.constructUse.constructLevel.icon(36.0), ), ), Container( diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart b/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart index bfd45071f..9266c63e4 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart @@ -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 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 { + 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 get _vocab => MatrixState + .pangeaController.getAnalytics.constructListModel + .constructList(type: ConstructTypeEnum.vocab) + .sorted((a, b) => a.lemma.toLowerCase().compareTo(b.lemma.toLowerCase())); + + List 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 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() + .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, + ); + }, + ), ), - ], - ), + ), + ], ); } }