Merge branch 'main' into 218-improve-interactive-translator

pull/1183/head
ggurdin 1 year ago committed by GitHub
commit 27c641e8e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -3,6 +3,7 @@ import 'dart:developer';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/construct_analytics_event.dart';
import 'package:fluffychat/pangea/models/student_analytics_summary_model.dart'; import 'package:fluffychat/pangea/models/student_analytics_summary_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -111,4 +112,48 @@ class MyAnalyticsController {
ErrorHandler.logError(e: err, s: s); ErrorHandler.logError(e: err, s: s);
} }
} }
// used to aggregate ConstructEvents, from multiple senders (students) with the same lemma
List<AggregateConstructUses> aggregateConstructData(
List<ConstructEvent> constructs,
) {
final Map<String, List<ConstructEvent>> lemmasToConstructs = {};
for (final construct in constructs) {
lemmasToConstructs[construct.content.lemma] ??= [];
lemmasToConstructs[construct.content.lemma]!.add(construct);
}
final List<AggregateConstructUses> aggregatedConstructs = [];
for (final lemmaToConstructs in lemmasToConstructs.entries) {
final List<ConstructEvent> lemmaConstructs = lemmaToConstructs.value;
final AggregateConstructUses aggregatedData = AggregateConstructUses(
constructs: lemmaConstructs,
);
aggregatedConstructs.add(aggregatedData);
}
return aggregatedConstructs;
}
}
class AggregateConstructUses {
final List<ConstructEvent> _constructs;
AggregateConstructUses({required List<ConstructEvent> constructs})
: _constructs = constructs;
String get lemma {
assert(
_constructs.isNotEmpty &&
_constructs.every(
(construct) =>
construct.content.lemma == _constructs.first.content.lemma,
),
);
return _constructs.first.content.lemma;
}
List<OneConstructUse> get uses => _constructs
.map((construct) => construct.content.uses)
.expand((element) => element)
.toList();
} }

@ -104,7 +104,6 @@ class AnalyticsListTileState extends State<AnalyticsListTile> {
) )
: null, : null,
selected: widget.selected, selected: widget.selected,
enabled: widget.enabled,
onTap: () { onTap: () {
(room?.isSpace ?? false) && widget.allowNavigateOnSelect (room?.isSpace ?? false) && widget.allowNavigateOnSelect
? context.go( ? context.go(

@ -5,6 +5,7 @@ import 'package:fluffychat/pangea/extensions/client_extension.dart';
import 'package:fluffychat/pangea/pages/analytics/base_analytics_view.dart'; import 'package:fluffychat/pangea/pages/analytics/base_analytics_view.dart';
import 'package:fluffychat/pangea/pages/analytics/student_analytics/student_analytics.dart'; import 'package:fluffychat/pangea/pages/analytics/student_analytics/student_analytics.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import '../../../widgets/matrix.dart'; import '../../../widgets/matrix.dart';
@ -101,18 +102,40 @@ class BaseAnalyticsController extends State<BaseAnalyticsPage> {
} }
} }
void toggleSelection(AnalyticsSelected selectedParam) { Future<void> toggleSelection(AnalyticsSelected selectedParam) async {
final bool joinSelectedRoom =
selectedParam.type == AnalyticsEntryType.room &&
!enableSelection(
selectedParam,
);
if (joinSelectedRoom) {
await showFutureLoadingDialog(
context: context,
future: () async {
final waitForRoom = Matrix.of(context).client.waitForRoomInSync(
selectedParam.id,
join: true,
);
await Matrix.of(context).client.joinRoom(selectedParam.id);
await waitForRoom;
},
);
}
setState(() { setState(() {
debugPrint("selectedParam.id is ${selectedParam.id}"); debugPrint("selectedParam.id is ${selectedParam.id}");
currentLemma = null; currentLemma = null;
selected = isSelected(selectedParam.id) ? null : selectedParam; selected = isSelected(selectedParam.id) ? null : selectedParam;
}); });
pangeaController.analytics.setConstructs( pangeaController.analytics.setConstructs(
constructType: ConstructType.grammar, constructType: ConstructType.grammar,
defaultSelected: widget.defaultSelected, defaultSelected: widget.defaultSelected,
selected: selected, selected: selected,
removeIT: true, removeIT: true,
); );
Future.delayed(Duration.zero, () => setState(() {})); Future.delayed(Duration.zero, () => setState(() {}));
} }

@ -145,6 +145,7 @@ class BaseAnalyticsView extends StatelessWidget {
) * ) *
72, 72,
child: TabBarView( child: TabBarView(
physics: const NeverScrollableScrollPhysics(),
children: [ children: [
Column( Column(
crossAxisAlignment: crossAxisAlignment:

@ -1,11 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'package:fluffychat/pangea/enum/time_span.dart';
import 'package:fluffychat/pangea/pages/analytics/class_list/class_list_view.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:fluffychat/pangea/enum/time_span.dart';
import 'package:fluffychat/pangea/pages/analytics/class_list/class_list_view.dart';
import '../../../../widgets/matrix.dart'; import '../../../../widgets/matrix.dart';
import '../../../constants/pangea_event_types.dart'; import '../../../constants/pangea_event_types.dart';
import '../../../controllers/pangea_controller.dart'; import '../../../controllers/pangea_controller.dart';
@ -42,7 +41,11 @@ class AnalyticsClassListController extends State<AnalyticsClassList> {
if (!(refreshTimer[newState.room.id]?.isActive ?? false)) { if (!(refreshTimer[newState.room.id]?.isActive ?? false)) {
refreshTimer[newState.room.id] = Timer( refreshTimer[newState.room.id] = Timer(
const Duration(seconds: 3), const Duration(seconds: 3),
() => updateClassAnalytics(context, newState.room), () {
if (newState.room.isSpace) {
updateClassAnalytics(context, newState.room);
}
},
); );
} }
} }

@ -3,9 +3,9 @@ import 'dart:async';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/construct_analytics_event.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_representation_event.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_representation_event.dart';
import 'package:fluffychat/pangea/models/constructs_analytics_model.dart'; import 'package:fluffychat/pangea/models/constructs_analytics_model.dart';
@ -169,7 +169,7 @@ class ConstructListViewState extends State<ConstructListView> {
int get lemmaIndex => int get lemmaIndex =>
constructs?.indexWhere( constructs?.indexWhere(
(element) => element.content.lemma == widget.controller.currentLemma, (element) => element.lemma == widget.controller.currentLemma,
) ?? ) ??
-1; -1;
@ -217,7 +217,7 @@ class ConstructListViewState extends State<ConstructListView> {
setState(() => fetchingUses = true); setState(() => fetchingUses = true);
try { try {
final List<OneConstructUse> uses = currentConstruct!.content.uses; final List<OneConstructUse> uses = currentConstruct!.uses;
_msgEvents.clear(); _msgEvents.clear();
for (final OneConstructUse use in uses) { for (final OneConstructUse use in uses) {
@ -236,16 +236,24 @@ class ConstructListViewState extends State<ConstructListView> {
ErrorHandler.logError( ErrorHandler.logError(
e: err, e: err,
s: s, s: s,
m: "Failed to fetch uses for current construct ${currentConstruct?.content.lemma}", m: "Failed to fetch uses for current construct ${currentConstruct?.lemma}",
); );
} }
} }
List<ConstructEvent>? get constructs => List<AggregateConstructUses>? get constructs =>
widget.pangeaController.analytics.constructs; widget.pangeaController.analytics.constructs != null
? widget.pangeaController.myAnalytics
ConstructEvent? get currentConstruct => constructs?.firstWhereOrNull( .aggregateConstructData(
(element) => element.content.lemma == widget.controller.currentLemma, widget.pangeaController.analytics.constructs!,
)
.sorted(
(a, b) => b.uses.length.compareTo(a.uses.length),
)
: null;
AggregateConstructUses? get currentConstruct => constructs?.firstWhereOrNull(
(element) => element.lemma == widget.controller.currentLemma,
); );
// given the current lemma and list of message events, return a list of // given the current lemma and list of message events, return a list of
@ -280,6 +288,13 @@ class ConstructListViewState extends State<ConstructListView> {
return allMsgErrorSteps; return allMsgErrorSteps;
} }
Future<void> showConstructMessagesDialog() async {
await showDialog<ConstructMessagesDialog>(
context: context,
builder: (c) => ConstructMessagesDialog(controller: this),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (!widget.init || fetchingUses) { if (!widget.init || fetchingUses) {
@ -294,58 +309,92 @@ class ConstructListViewState extends State<ConstructListView> {
); );
} }
final msgEventMatches = getMessageEventMatches(); return Expanded(
child: ListView.builder(
itemCount: constructs!.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
constructs![index].lemma,
),
subtitle: Text(
'${L10n.of(context)!.total} ${constructs![index].uses.length}',
),
onTap: () async {
final String lemma = constructs![index].lemma;
widget.controller.setCurrentLemma(lemma);
fetchUses().then((_) => showConstructMessagesDialog());
},
);
},
),
);
}
}
return widget.controller.currentLemma == null class ConstructMessagesDialog extends StatelessWidget {
? Expanded( final ConstructListViewState controller;
child: ListView.builder( const ConstructMessagesDialog({
itemCount: constructs!.length, super.key,
itemBuilder: (context, index) { required this.controller,
return ListTile( });
title: Text(
constructs![index].content.lemma, @override
), Widget build(BuildContext context) {
subtitle: Text( if (controller.widget.controller.currentLemma == null) {
'${L10n.of(context)!.total} ${constructs![index].content.uses.length}', return const AlertDialog(content: CircularProgressIndicator.adaptive());
), }
onTap: () {
final String lemma = constructs![index].content.lemma; final msgEventMatches = controller.getMessageEventMatches();
widget.controller.setCurrentLemma(lemma);
fetchUses(); return AlertDialog(
}, title: Center(child: Text(controller.widget.controller.currentLemma!)),
); content: Column(
}, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (controller.constructs![controller.lemmaIndex].uses.length >
controller._msgEvents.length)
Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(L10n.of(context)!.roomDataMissing),
),
), ),
) SingleChildScrollView(
: Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (constructs![lemmaIndex].content.uses.length > ...msgEventMatches.mapIndexed(
_msgEvents.length) (index, event) => Column(
Center( children: [
child: Padding( ConstructMessage(
padding: const EdgeInsets.all(8.0), msgEvent: event.msgEvent,
child: Text(L10n.of(context)!.roomDataMissing), lemma: controller.widget.controller.currentLemma!,
), errorMessage: event.lemmaMatch,
), ),
Expanded( if (index < msgEventMatches.length - 1)
child: ListView.separated(
separatorBuilder: (context, index) =>
const Divider(height: 1), const Divider(height: 1),
itemCount: msgEventMatches.length, ],
itemBuilder: (context, index) {
return ConstructMessage(
msgEvent: msgEventMatches[index].msgEvent,
lemma: widget.controller.currentLemma!,
errorMessage: msgEventMatches[index].lemmaMatch,
);
},
), ),
), ),
], ],
), ),
); ),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context, rootNavigator: false).pop(),
child: Text(
L10n.of(context)!.close.toUpperCase(),
style: TextStyle(
color:
Theme.of(context).textTheme.bodyMedium?.color?.withAlpha(150),
),
),
),
],
);
} }
} }

Loading…
Cancel
Save