initial work on expanding popup to the side

This commit is contained in:
ggurdin 2024-11-07 14:42:55 -05:00
parent 615ed7ad98
commit 2dac558e1a
No known key found for this signature in database
GPG key ID: A01CB41737CBB478
4 changed files with 198 additions and 231 deletions

View file

@ -1,230 +0,0 @@
import 'dart:math';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
import 'package:fluffychat/pangea/enum/progress_indicators_enum.dart';
import 'package:fluffychat/pangea/models/analytics/construct_list_model.dart';
import 'package:fluffychat/pangea/utils/get_grammar_copy.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class AnalyticsPopup extends StatelessWidget {
final ProgressIndicatorEnum indicator;
final ConstructListModel constructsModel;
final bool showGroups;
const AnalyticsPopup({
required this.indicator,
required this.constructsModel,
this.showGroups = true,
super.key,
});
List<MapEntry<String, List<ConstructUses>>> get categoriesToUses {
final entries = constructsModel.categoriesToUses.entries.toList();
// Sort the list with custom logic
entries.sort((a, b) {
// Check if one of the keys is 'Other'
if (a.key == 'Other') return 1;
if (b.key == 'Other') return -1;
// Sort by the length of the list in descending order
final aTotalPoints = a.value.fold<int>(
0,
(previousValue, element) => previousValue + element.points,
);
final bTotalPoints = b.value.fold<int>(
0,
(previousValue, element) => previousValue + element.points,
);
return bTotalPoints.compareTo(aTotalPoints);
});
return entries;
}
@override
Widget build(BuildContext context) {
Widget? dialogContent;
final bool hasNoData = constructsModel.constructListWithPoints.isEmpty;
final bool hasNoCategories = constructsModel.categoriesToUses.length == 1 &&
constructsModel.categoriesToUses.keys.first == "Other";
if (hasNoData) {
dialogContent = Center(child: Text(L10n.of(context)!.noDataFound));
} else if (hasNoCategories || !showGroups) {
dialogContent = ListView.builder(
itemCount: constructsModel.constructListWithPoints.length,
itemBuilder: (context, index) {
return ConstructUsesXPTile(
indicator: indicator,
constructsModel: constructsModel,
constructUses: constructsModel.constructListWithPoints[index],
);
},
);
} else {
dialogContent = ListView.builder(
itemCount: categoriesToUses.length,
itemBuilder: (context, index) {
final category = categoriesToUses[index];
return Column(
children: [
ConstructUsesExpansionTile(
indicator: indicator,
constructsModel: constructsModel,
category: category.key,
constructUses: category.value,
),
const Divider(height: 1),
],
);
},
);
}
return Dialog(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 400,
maxHeight: 600,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: Scaffold(
appBar: AppBar(
title: Text(indicator.tooltip(context)),
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: Navigator.of(context).pop,
),
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: dialogContent,
),
),
),
),
);
}
}
class ConstructUsesXPTile extends StatelessWidget {
final ProgressIndicatorEnum indicator;
final ConstructListModel constructsModel;
final ConstructUses constructUses;
const ConstructUsesXPTile({
required this.indicator,
required this.constructsModel,
required this.constructUses,
super.key,
});
@override
Widget build(BuildContext context) {
return Tooltip(
message: "${constructUses.points} / ${constructsModel.maxXPPerLemma}",
child: ListTile(
onTap: () {},
title: Text(
constructsModel.type == ConstructTypeEnum.morph
? getGrammarCopy(
category: constructUses.category,
lemma: constructUses.lemma,
context: context,
) ??
constructUses.lemma
: constructUses.lemma,
),
subtitle: Row(
children: [
Expanded(
child: LinearProgressIndicator(
value: constructUses.points / constructsModel.maxXPPerLemma,
minHeight: 20,
borderRadius: const BorderRadius.all(
Radius.circular(AppConfig.borderRadius),
),
color: indicator.color(context),
),
),
const SizedBox(width: 12),
Text("${constructUses.points}xp"),
],
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
),
),
);
}
}
class ConstructUsesExpansionTile extends StatefulWidget {
final ProgressIndicatorEnum indicator;
final ConstructListModel constructsModel;
final String category;
final List<ConstructUses> constructUses;
const ConstructUsesExpansionTile({
required this.indicator,
required this.constructsModel,
required this.category,
required this.constructUses,
super.key,
});
@override
ConstructUsesExpansionTileState createState() =>
ConstructUsesExpansionTileState();
}
class ConstructUsesExpansionTileState
extends State<ConstructUsesExpansionTile> {
int _lastLoadedIndex = 50;
int get endIndex => min(_lastLoadedIndex, widget.constructUses.length);
@override
Widget build(BuildContext context) {
final List<Widget> xpTiles = widget.constructUses
.sublist(0, endIndex)
.map((constructUses) {
return ConstructUsesXPTile(
indicator: widget.indicator,
constructsModel: widget.constructsModel,
constructUses: constructUses,
);
})
.cast<Widget>()
.toList();
if (widget.constructUses.length > _lastLoadedIndex) {
xpTiles.add(
Padding(
padding: const EdgeInsets.all(10),
child: TextButton(
child: Text(L10n.of(context)!.loadMore),
onPressed: () => setState(() => _lastLoadedIndex += 50),
),
),
);
}
return ExpansionTile(
title: Text(
widget.constructsModel.type?.getDisplayCopy(
widget.category,
context,
) ??
widget.category,
),
children: xpTiles,
onExpansionChanged: (expanded) {
if (expanded) {
setState(() => _lastLoadedIndex = 50);
}
},
);
}
}

View file

@ -0,0 +1,136 @@
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
import 'package:fluffychat/pangea/enum/progress_indicators_enum.dart';
import 'package:fluffychat/pangea/models/analytics/construct_list_model.dart';
import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/analytics_popup/analytics_xp_tile.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class AnalyticsPopup extends StatefulWidget {
final ProgressIndicatorEnum indicator;
final ConstructListModel constructsModel;
final bool showGroups;
const AnalyticsPopup({
required this.indicator,
required this.constructsModel,
this.showGroups = true,
super.key,
});
@override
AnalyticsPopupState createState() => AnalyticsPopupState();
}
class AnalyticsPopupState extends State<AnalyticsPopup> {
String? selectedCategory;
List<MapEntry<String, List<ConstructUses>>> get categoriesToUses {
final entries = widget.constructsModel.categoriesToUses.entries.toList();
// Sort the list with custom logic
entries.sort((a, b) {
// Check if one of the keys is 'Other'
if (a.key == 'Other') return 1;
if (b.key == 'Other') return -1;
// Sort by the length of the list in descending order
final aTotalPoints = a.value.fold<int>(
0,
(previousValue, element) => previousValue + element.points,
);
final bTotalPoints = b.value.fold<int>(
0,
(previousValue, element) => previousValue + element.points,
);
return bTotalPoints.compareTo(aTotalPoints);
});
return entries;
}
void setSelectedCategory(String? category) => setState(() {
selectedCategory = category;
});
@override
Widget build(BuildContext context) {
Widget? dialogContent;
final bool hasNoData =
widget.constructsModel.constructListWithPoints.isEmpty;
final bool hasNoCategories =
widget.constructsModel.categoriesToUses.length == 1 &&
widget.constructsModel.categoriesToUses.keys.first == "Other";
if (selectedCategory != null) {
dialogContent = ListView.builder(
itemCount:
widget.constructsModel.categoriesToUses[selectedCategory]!.length,
itemBuilder: (context, index) {
final constructUses =
widget.constructsModel.categoriesToUses[selectedCategory]![index];
return ConstructUsesXPTile(constructUses);
},
);
} else if (hasNoData) {
dialogContent = Center(child: Text(L10n.of(context)!.noDataFound));
} else if (hasNoCategories || !widget.showGroups) {
dialogContent = ListView.builder(
itemCount: widget.constructsModel.constructListWithPoints.length,
itemBuilder: (context, index) {
final constructUses =
widget.constructsModel.constructListWithPoints[index];
return ConstructUsesXPTile(constructUses);
},
);
} else {
dialogContent = ListView.builder(
itemCount: categoriesToUses.length,
itemBuilder: (context, index) {
final category = categoriesToUses[index];
final copy = widget.constructsModel.type?.getDisplayCopy(
category.key,
context,
) ??
category.key;
return Column(
children: [
ListTile(
title: Text(copy),
trailing: const Icon(Icons.chevron_right_outlined),
onTap: () => setSelectedCategory(category.key),
),
const Divider(height: 1),
],
);
},
);
}
return Dialog(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 400,
maxHeight: 600,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: Scaffold(
appBar: AppBar(
title: Text(widget.indicator.tooltip(context)),
leading: IconButton(
icon: selectedCategory == null
? const Icon(Icons.close)
: const Icon(Icons.chevron_left_outlined),
onPressed: selectedCategory == null
? Navigator.of(context).pop
: () => setSelectedCategory(null),
),
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: dialogContent,
),
),
),
),
);
}
}

View file

@ -0,0 +1,61 @@
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
import 'package:fluffychat/pangea/enum/progress_indicators_enum.dart';
import 'package:fluffychat/pangea/models/analytics/construct_list_model.dart';
import 'package:fluffychat/pangea/utils/get_grammar_copy.dart';
import 'package:flutter/material.dart';
class ConstructUsesXPTile extends StatelessWidget {
final ConstructUses constructUses;
const ConstructUsesXPTile(
this.constructUses, {
super.key,
});
@override
Widget build(BuildContext context) {
final ProgressIndicatorEnum indicator =
constructUses.constructType == ConstructTypeEnum.morph
? ProgressIndicatorEnum.morphsUsed
: ProgressIndicatorEnum.wordsUsed;
return Tooltip(
message:
"${constructUses.points} / ${constructUses.constructType.maxXPPerLemma}",
child: ListTile(
onTap: () {},
title: Text(
constructUses.constructType == ConstructTypeEnum.morph
? getGrammarCopy(
category: constructUses.category,
lemma: constructUses.lemma,
context: context,
) ??
constructUses.lemma
: constructUses.lemma,
),
subtitle: Row(
children: [
Expanded(
child: LinearProgressIndicator(
value: constructUses.points /
constructUses.constructType.maxXPPerLemma,
minHeight: 20,
borderRadius: const BorderRadius.all(
Radius.circular(AppConfig.borderRadius),
),
color: indicator.color(context),
),
),
const SizedBox(width: 12),
Text("${constructUses.points}xp"),
],
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
),
),
);
}
}

View file

@ -9,7 +9,7 @@ import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart';
import 'package:fluffychat/pangea/widgets/animations/progress_bar/progress_bar.dart';
import 'package:fluffychat/pangea/widgets/animations/progress_bar/progress_bar_details.dart';
import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/analytics_popup.dart';
import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/analytics_popup/analytics_popup.dart';
import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart';
import 'package:fluffychat/pangea/widgets/flag.dart';
import 'package:fluffychat/widgets/matrix.dart';