From c5187c7639b3e4745a5649227de2346e40c43c20 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 29 Jul 2024 14:52:09 -0400 Subject: [PATCH] added learning summary to chat list, removed references to summary analytics --- assets/l10n/intl_en.arb | 5 +- lib/config/routes.dart | 24 +- lib/pages/chat/chat.dart | 2 +- lib/pages/chat_list/chat_list_body.dart | 4 + .../chat_list/client_chooser_button.dart | 35 +- .../message_analytics_controller.dart | 575 +++----------- .../controllers/my_analytics_controller.dart | 24 +- lib/pangea/enum/progress_indicators_enum.dart | 54 ++ lib/pangea/enum/time_span.dart | 59 +- .../client_analytics_extension.dart | 1 - .../events_extension.dart | 86 +- .../pangea_room_extension.dart | 19 +- .../room_analytics_extension.dart | 46 +- .../models/analytics/analytics_event.dart | 29 - .../models/analytics/analytics_model.dart | 23 - .../analytics/chart_analytics_model.dart | 252 +++--- .../models/analytics/constructs_event.dart | 11 +- .../models/analytics/constructs_model.dart | 3 +- .../analytics/summary_analytics_event.dart | 21 - .../analytics/summary_analytics_model.dart | 111 --- .../pages/analytics/analytics_list_tile.dart | 282 +++---- .../pages/analytics/base_analytics.dart | 364 ++++----- .../pages/analytics/base_analytics_view.dart | 476 +++++------ .../pages/analytics/construct_list.dart | 100 +-- .../analytics/list_summary_analytics.dart | 184 ++--- .../pages/analytics/messages_bar_chart.dart | 740 +++++++++--------- .../space_analytics/space_analytics.dart | 207 +++-- .../space_analytics/space_analytics_view.dart | 124 +-- .../analytics/space_list/space_list.dart | 172 ++-- .../analytics/space_list/space_list_view.dart | 172 ++-- .../student_analytics/student_analytics.dart | 156 ++-- .../student_analytics_view.dart | 124 +-- .../learning_progress_indicators.dart | 195 +++++ .../analytics_summary/progress_indicator.dart | 51 ++ lib/utils/client_manager.dart | 1 - 35 files changed, 2145 insertions(+), 2587 deletions(-) create mode 100644 lib/pangea/enum/progress_indicators_enum.dart delete mode 100644 lib/pangea/models/analytics/analytics_event.dart delete mode 100644 lib/pangea/models/analytics/analytics_model.dart delete mode 100644 lib/pangea/models/analytics/summary_analytics_event.dart delete mode 100644 lib/pangea/models/analytics/summary_analytics_model.dart create mode 100644 lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart create mode 100644 lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 385009ff6..eaccf93a4 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4113,5 +4113,8 @@ "createSpace": "Create space", "createChat": "Create chat", "error520Title": "Please try again.", - "error520Desc": "Sorry, we could not understand your message..." + "error520Desc": "Sorry, we could not understand your message...", + "wordsUsed": "Words Used", + "errorTypes": "Error Types", + "level": "Level" } \ No newline at end of file diff --git a/lib/config/routes.dart b/lib/config/routes.dart index d9c7a70f7..1cf7c70f2 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -26,9 +26,7 @@ import 'package:fluffychat/pages/settings_notifications/settings_notifications.d import 'package:fluffychat/pages/settings_password/settings_password.dart'; import 'package:fluffychat/pages/settings_security/settings_security.dart'; import 'package:fluffychat/pages/settings_style/settings_style.dart'; -import 'package:fluffychat/pangea/enum/bar_chart_view_enum.dart'; import 'package:fluffychat/pangea/guard/p_vguard.dart'; -import 'package:fluffychat/pangea/pages/analytics/student_analytics/student_analytics.dart'; import 'package:fluffychat/pangea/pages/find_partner/find_partner.dart'; import 'package:fluffychat/pangea/pages/p_user_age/p_user_age.dart'; import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart'; @@ -162,17 +160,17 @@ abstract class AppRoutes { ), routes: [ // #Pangea - GoRoute( - path: 'mylearning', - pageBuilder: (context, state) => defaultPageBuilder( - context, - state, - const StudentAnalyticsPage( - selectedView: BarChartViewSelection.messages, - ), - ), - redirect: loggedOutRedirect, - ), + // GoRoute( + // path: 'mylearning', + // pageBuilder: (context, state) => defaultPageBuilder( + // context, + // state, + // const StudentAnalyticsPage( + // selectedView: BarChartViewSelection.messages, + // ), + // ), + // redirect: loggedOutRedirect, + // ), // GoRoute( // path: 'analytics', // pageBuilder: (context, state) => defaultPageBuilder( diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index aa2b78d4d..f4ab25c6f 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -475,7 +475,7 @@ class ChatController extends State // Pangea# if (kIsWeb && !Matrix.of(context).webHasFocus) return; // #Pangea - } catch (err, s) { + } catch (err) { return; } // Pangea# diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index b9b388da9..1993a97a6 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -3,6 +3,7 @@ import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart'; import 'package:fluffychat/pages/chat_list/space_view.dart'; import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart'; +import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart'; import 'package:fluffychat/pangea/widgets/chat_list/chat_list_body_text.dart'; import 'package:fluffychat/pangea/widgets/chat_list/chat_list_header_wrapper.dart'; import 'package:fluffychat/pangea/widgets/chat_list/chat_list_item_wrapper.dart'; @@ -164,6 +165,9 @@ class ChatListViewBody extends StatelessWidget { title: L10n.of(context)!.chats, icon: const Icon(Icons.forum_outlined), ), + // #Pangea + const LearningProgressIndicators(), + // Pangea# if (client.prevBatch != null && rooms.isEmpty && !controller.isSearchMode) ...[ diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index 15f52ebe2..a6bf71e54 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -1,5 +1,4 @@ import 'package:adaptive_dialog/adaptive_dialog.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/utils/find_conversation_partner_dialog.dart'; import 'package:fluffychat/pangea/utils/logout.dart'; import 'package:fluffychat/pangea/utils/space_code.dart'; @@ -67,19 +66,19 @@ class ClientChooserButton extends StatelessWidget { // ], // ), // ), - PopupMenuItem( - enabled: matrix.client.rooms.any( - (room) => !room.isSpace && !room.isArchived && !room.isAnalyticsRoom, - ), - value: SettingsAction.myAnalytics, - child: Row( - children: [ - const Icon(Icons.analytics_outlined), - const SizedBox(width: 18), - Expanded(child: Text(L10n.of(context)!.myLearning)), - ], - ), - ), + // PopupMenuItem( + // enabled: matrix.client.rooms.any( + // (room) => !room.isSpace && !room.isArchived && !room.isAnalyticsRoom, + // ), + // value: SettingsAction.myAnalytics, + // child: Row( + // children: [ + // const Icon(Icons.analytics_outlined), + // const SizedBox(width: 18), + // Expanded(child: Text(L10n.of(context)!.myLearning)), + // ], + // ), + // ), PopupMenuItem( value: SettingsAction.newClass, child: Row( @@ -404,9 +403,9 @@ class ClientChooserButton extends StatelessWidget { // case SettingsAction.spaceAnalytics: // context.go('/rooms/analytics'); // break; - case SettingsAction.myAnalytics: - context.go('/rooms/mylearning'); - break; + // case SettingsAction.myAnalytics: + // context.go('/rooms/mylearning'); + // break; case SettingsAction.logout: pLogoutAction(context); break; @@ -497,7 +496,7 @@ enum SettingsAction { learning, joinWithClassCode, // spaceAnalytics, - myAnalytics, + // myAnalytics, findAConversationPartner, logout, newClass, diff --git a/lib/pangea/controllers/message_analytics_controller.dart b/lib/pangea/controllers/message_analytics_controller.dart index 32aaf66fc..1883e96dc 100644 --- a/lib/pangea/controllers/message_analytics_controller.dart +++ b/lib/pangea/controllers/message_analytics_controller.dart @@ -1,16 +1,9 @@ import 'dart:async'; -import 'dart:developer'; -import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/constants/match_rule_ids.dart'; -import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; -import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/enum/time_span.dart'; -import 'package:fluffychat/pangea/models/analytics/analytics_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_event.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_event.dart'; -import 'package:fluffychat/pangea/models/language_model.dart'; import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; @@ -20,89 +13,87 @@ import 'package:sentry_flutter/sentry_flutter.dart'; import '../constants/class_default_values.dart'; import '../extensions/client_extension/client_extension.dart'; import '../extensions/pangea_room_extension/pangea_room_extension.dart'; -import '../models/analytics/chart_analytics_model.dart'; import 'base_controller.dart'; import 'pangea_controller.dart'; // controls the fetching of analytics data class AnalyticsController extends BaseController { late PangeaController _pangeaController; - - final List _cachedAnalyticsModels = []; final List _cachedConstructs = []; AnalyticsController(PangeaController pangeaController) : super() { _pangeaController = pangeaController; } - ///////// TIME SPANS ////////// - String get _analyticsTimeSpanKey => "ANALYTICS_TIME_SPAN_KEY"; + String get langCode => + _pangeaController.languageController.userL2?.langCode ?? + _pangeaController.pLanguageStore.targetOptions.first.langCode; - TimeSpan get currentAnalyticsTimeSpan { - try { - final String? str = _pangeaController.pStoreService.read( - _analyticsTimeSpanKey, - ); - return str != null - ? TimeSpan.values.firstWhere((e) { - final spanString = e.toString(); - return spanString == str; - }) - : ClassDefaultValues.defaultTimeSpan; - } catch (err) { - debugger(when: kDebugMode); - return ClassDefaultValues.defaultTimeSpan; - } - } + // String get _analyticsTimeSpanKey => "ANALYTICS_TIME_SPAN_KEY"; - Future setCurrentAnalyticsTimeSpan(TimeSpan timeSpan) async { - await _pangeaController.pStoreService.save( - _analyticsTimeSpanKey, - timeSpan.toString(), - ); - setState(); - } + // TimeSpan get currentAnalyticsTimeSpan { + // try { + // final String? str = _pangeaController.pStoreService.read( + // _analyticsTimeSpanKey, + // ); + // return str != null + // ? TimeSpan.values.firstWhere((e) { + // final spanString = e.toString(); + // return spanString == str; + // }) + // : ClassDefaultValues.defaultTimeSpan; + // } catch (err) { + // debugger(when: kDebugMode); + // return ClassDefaultValues.defaultTimeSpan; + // } + // } - ///////// SPACE ANALYTICS LANGUAGES ////////// - String get _analyticsSpaceLangKey => "ANALYTICS_SPACE_LANG_KEY"; + // Future setCurrentAnalyticsTimeSpan(TimeSpan timeSpan) async { + // await _pangeaController.pStoreService.save( + // _analyticsTimeSpanKey, + // timeSpan.toString(), + // ); + // setState(); + // } - LanguageModel get currentAnalyticsLang { - try { - final String? str = _pangeaController.pStoreService.read( - _analyticsSpaceLangKey, - ); - return str != null - ? PangeaLanguage.byLangCode(str) - : _pangeaController.languageController.userL2 ?? - _pangeaController.pLanguageStore.targetOptions.first; - } catch (err) { - debugger(when: kDebugMode); - return _pangeaController.pLanguageStore.targetOptions.first; - } - } + // String get _analyticsSpaceLangKey => "ANALYTICS_SPACE_LANG_KEY"; - Future setCurrentAnalyticsLang(LanguageModel lang) async { - await _pangeaController.pStoreService.save( - _analyticsSpaceLangKey, - lang.langCode, - ); - setState(); - } + // LanguageModel get currentAnalyticsLang { + // try { + // final String? str = _pangeaController.pStoreService.read( + // _analyticsSpaceLangKey, + // ); + // return str != null + // ? PangeaLanguage.byLangCode(str) + // : _pangeaController.languageController.userL2 ?? + // _pangeaController.pLanguageStore.targetOptions.first; + // } catch (err) { + // debugger(when: kDebugMode); + // return _pangeaController.pLanguageStore.targetOptions.first; + // } + // } - /// given an analytics event type and the current analytics language, - /// get the last time the user updated their analytics - Future myAnalyticsLastUpdated(String type) async { - final List analyticsRooms = _pangeaController - .matrixState.client.allMyAnalyticsRooms - .where((room) => room.isAnalyticsRoom) - .toList(); + // Future setCurrentAnalyticsLang(LanguageModel lang) async { + // await _pangeaController.pStoreService.save( + // _analyticsSpaceLangKey, + // lang.langCode, + // ); + // setState(); + // } + + /// Get the last time the user updated their analytics. + /// Tries to get the last time the user updated analytics for their current L2. + /// If there isn't yet an analytics room reacted for their L2, checks if the + /// user has any other analytics rooms and returns the most recent update time. + Future myAnalyticsLastUpdated() async { + final List analyticsRooms = + _pangeaController.matrixState.client.allMyAnalyticsRooms; final Map langCodeLastUpdates = {}; for (final Room analyticsRoom in analyticsRooms) { final String? roomLang = analyticsRoom.madeForLang; if (roomLang == null) continue; final DateTime? lastUpdated = await analyticsRoom.analyticsLastUpdated( - type, _pangeaController.matrixState.client.userID!, ); if (lastUpdated != null) { @@ -121,25 +112,20 @@ class AnalyticsController extends BaseController { ); } + /// check if any students have recently updated their analytics + /// if any have, then the cache needs to be updated Future spaceAnalyticsLastUpdated( - String type, Room space, ) async { - // check if any students have recently updated their analytics - // if any have, then the cache needs to be updated - // TODO - figure out how to do this on a per-student basis await space.requestParticipants(); final List> lastUpdatedFutures = []; for (final student in space.students) { final Room? analyticsRoom = _pangeaController.matrixState.client - .analyticsRoomLocal(currentAnalyticsLang.langCode, student.id); + .analyticsRoomLocal(langCode, student.id); if (analyticsRoom == null) continue; lastUpdatedFutures.add( - analyticsRoom.analyticsLastUpdated( - type, - student.id, - ), + analyticsRoom.analyticsLastUpdated(student.id), ); } @@ -155,372 +141,16 @@ class AnalyticsController extends BaseController { return null; } - // Map of space ids to the last fetched hierarchy. Used when filtering - // private chat analytics to determine which children are already visible - // in the chat list - final Map> _lastFetchedHierarchies = {}; - - void setLatestHierarchy(String spaceId, GetSpaceHierarchyResponse resp) { - final List roomIds = resp.rooms.map((room) => room.roomId).toList(); - _lastFetchedHierarchies[spaceId] = roomIds; - } - - Future> getLatestSpaceHierarchy(String spaceId) async { - if (!_lastFetchedHierarchies.containsKey(spaceId)) { - final resp = - await _pangeaController.matrixState.client.getSpaceHierarchy(spaceId); - setLatestHierarchy(spaceId, resp); - } - return _lastFetchedHierarchies[spaceId] ?? []; - } - - //////////////////////////// MESSAGE SUMMARY ANALYTICS //////////////////////////// - - /// get all the summary analytics events for the current user - /// in the current language's analytics room - Future> mySummaryAnalytics() async { - final Room? analyticsRoom = _pangeaController.matrixState.client - .analyticsRoomLocal(currentAnalyticsLang.langCode); - if (analyticsRoom == null) return []; - - final List? roomEvents = - await analyticsRoom.getAnalyticsEvents( - type: PangeaEventTypes.summaryAnalytics, - since: currentAnalyticsTimeSpan.cutOffDate, - userId: _pangeaController.matrixState.client.userID!, - ); - return roomEvents?.cast() ?? []; - } - - Future> spaceMemberAnalytics( - Room space, + Future> allMyConstructs( + TimeSpan timeSpan, ) async { - // gets all the summary analytics events for the students - // in a space since the current timespace's cut off date - - // ensure that the participants of the space are loaded - await space.requestParticipants(); - - // TODO switch to using list of futures - final List analyticsEvents = []; - for (final student in space.students) { - final Room? analyticsRoom = _pangeaController.matrixState.client - .analyticsRoomLocal(currentAnalyticsLang.langCode, student.id); - - if (analyticsRoom != null) { - final List? roomEvents = - await analyticsRoom.getAnalyticsEvents( - type: PangeaEventTypes.summaryAnalytics, - since: currentAnalyticsTimeSpan.cutOffDate, - userId: student.id, - ); - analyticsEvents.addAll( - roomEvents?.cast() ?? [], - ); - } - } - - final List spaceChildrenIds = space.allSpaceChildRoomIds; - - // filter out the analyics events that don't belong to the space's children - final List allAnalyticsEvents = []; - for (final analyticsEvent in analyticsEvents) { - analyticsEvent.content.messages.removeWhere( - (msg) => !spaceChildrenIds.contains(msg.chatId), - ); - allAnalyticsEvents.add(analyticsEvent); - } - - return allAnalyticsEvents; - } - - ChartAnalyticsModel? getAnalyticsLocal({ - TimeSpan? timeSpan, - required AnalyticsSelected defaultSelected, - AnalyticsSelected? selected, - bool forceUpdate = false, - bool updateExpired = false, - DateTime? lastUpdated, - }) { - timeSpan ??= currentAnalyticsTimeSpan; - final int index = _cachedAnalyticsModels.indexWhere( - (e) => - (e.timeSpan == timeSpan) && - (e.defaultSelected.id == defaultSelected.id) && - (e.defaultSelected.type == defaultSelected.type) && - (e.selected?.id == selected?.id) && - (e.selected?.type == selected?.type) && - (e.langCode == currentAnalyticsLang.langCode), - ); - - if (index != -1) { - if ((updateExpired && _cachedAnalyticsModels[index].isExpired) || - forceUpdate || - _cachedAnalyticsModels[index].needsUpdate(lastUpdated)) { - _cachedAnalyticsModels.removeAt(index); - } else { - return _cachedAnalyticsModels[index].chartAnalyticsModel; - } - } - - return null; - } - - void cacheAnalytics({ - required ChartAnalyticsModel chartAnalyticsModel, - required AnalyticsSelected defaultSelected, - AnalyticsSelected? selected, - TimeSpan? timeSpan, - }) { - _cachedAnalyticsModels.add( - AnalyticsCacheModel( - timeSpan: timeSpan ?? currentAnalyticsTimeSpan, - chartAnalyticsModel: chartAnalyticsModel, - defaultSelected: defaultSelected, - selected: selected, - langCode: currentAnalyticsLang.langCode, - ), - ); - } - - List filterStudentAnalytics( - List unfiltered, - String? studentId, - ) { - final List filtered = - List.from(unfiltered); - filtered.removeWhere((e) => e.event.senderId != studentId); - return filtered; - } - - Future> filterRoomAnalytics( - List unfiltered, - String? roomID, - ) async { - List filtered = [...unfiltered]; - Room? room; - if (roomID != null) { - room = _pangeaController.matrixState.client.getRoomById(roomID); - if (room?.isSpace == true) { - return await filterSpaceAnalytics(unfiltered, roomID); - } - } - - filtered = filtered - .where( - (e) => (e.content).messages.any((u) => u.chatId == roomID), - ) - .toList(); - filtered.forEachIndexed( - (i, _) => (filtered[i].content).messages.removeWhere( - (u) => u.chatId != roomID, - ), - ); - return filtered; - } - - Future> filterPrivateChatAnalytics( - List unfiltered, - Room space, - ) async { - final List privateChatIds = space.allSpaceChildRoomIds; - final List lastFetched = await getLatestSpaceHierarchy(space.id); - for (final id in lastFetched) { - privateChatIds.removeWhere((e) => e == id); - } - - List filtered = - List.from(unfiltered); - filtered = filtered.where((e) { - return (e.content).messages.any( - (u) => privateChatIds.contains(u.chatId), - ); - }).toList(); - filtered.forEachIndexed( - (i, _) => (filtered[i].content).messages.removeWhere( - (u) => !privateChatIds.contains(u.chatId), - ), - ); - return filtered; - } - - Future> filterSpaceAnalytics( - List unfiltered, - String spaceId, - ) async { - final List chatIds = await getLatestSpaceHierarchy(spaceId); - List filtered = - List.from(unfiltered); - - filtered = filtered - .where( - (e) => e.content.messages.any((u) => chatIds.contains(u.chatId)), - ) - .toList(); - - filtered.forEachIndexed( - (i, _) => (filtered[i].content).messages.removeWhere( - (u) => !chatIds.contains(u.chatId), - ), - ); - return filtered; - } - - Future> filterAnalytics({ - required List unfilteredAnalytics, - required AnalyticsSelected defaultSelected, - Room? space, - AnalyticsSelected? selected, - }) async { - for (int i = 0; i < unfilteredAnalytics.length; i++) { - unfilteredAnalytics[i].content.messages.removeWhere( - (record) => record.time.isBefore( - currentAnalyticsTimeSpan.cutOffDate, - ), - ); - } - - switch (selected?.type) { - case null: - return unfilteredAnalytics; - case AnalyticsEntryType.student: - if (defaultSelected.type != AnalyticsEntryType.space) { - throw Exception( - "student filtering not available for default filter ${defaultSelected.type}", - ); - } - return filterStudentAnalytics(unfilteredAnalytics, selected?.id); - case AnalyticsEntryType.room: - return filterRoomAnalytics(unfilteredAnalytics, selected?.id); - case AnalyticsEntryType.privateChats: - if (defaultSelected.type == AnalyticsEntryType.student) { - throw "private chat filtering not available for my analytics"; - } - if (space == null) { - throw "space is null in filterAnalytics with selected type privateChats"; - } - return await filterPrivateChatAnalytics( - unfilteredAnalytics, - space, - ); - case AnalyticsEntryType.space: - return await filterSpaceAnalytics(unfilteredAnalytics, selected!.id); - default: - throw Exception("invalid filter type - ${selected?.type}"); - } - } - - Future getAnalytics({ - required AnalyticsSelected defaultSelected, - AnalyticsSelected? selected, - bool forceUpdate = false, - }) async { - try { - await _pangeaController.matrixState.client.roomsLoading; - - // if the user is looking at space analytics, then fetch the space - Room? space; - if (defaultSelected.type == AnalyticsEntryType.space) { - space = _pangeaController.matrixState.client.getRoomById( - defaultSelected.id, - ); - if (space == null) { - ErrorHandler.logError( - m: "space not found in getAnalytics", - data: { - "defaultSelected": defaultSelected, - "selected": selected, - }, - ); - return ChartAnalyticsModel( - msgs: [], - timeSpan: currentAnalyticsTimeSpan, - ); - } - } - - DateTime? lastUpdated; - if (defaultSelected.type != AnalyticsEntryType.space) { - // if default selected view is my analytics, check for the last - // time the logged in user updated their analytics events - // this gets passed to getAnalyticsLocal to determine if the cached - // entry is out-of-date - lastUpdated = await myAnalyticsLastUpdated( - PangeaEventTypes.summaryAnalytics, - ); - } else { - // else, get the last time a student in the space updated their analytics - lastUpdated = await spaceAnalyticsLastUpdated( - PangeaEventTypes.summaryAnalytics, - space!, - ); - } - - final ChartAnalyticsModel? local = getAnalyticsLocal( - defaultSelected: defaultSelected, - selected: selected, - forceUpdate: forceUpdate, - lastUpdated: lastUpdated, - ); - if (local != null && !forceUpdate) { - debugPrint("returning local analytics"); - return local; - } - debugPrint("fetching new analytics"); - - // get all the relevant summary analytics events for the current timespan - final List summaryEvents = - defaultSelected.type == AnalyticsEntryType.space - ? await spaceMemberAnalytics(space!) - : await mySummaryAnalytics(); - - // filter out the analytics events based on filters the user has chosen - final List filteredAnalytics = - await filterAnalytics( - unfilteredAnalytics: summaryEvents, - defaultSelected: defaultSelected, - space: space, - selected: selected, - ); - - // then create and return the model to be displayed - final ChartAnalyticsModel newModel = ChartAnalyticsModel( - timeSpan: currentAnalyticsTimeSpan, - msgs: filteredAnalytics - .map((event) => event.content.messages) - .expand((msgs) => msgs) - .toList(), - ); - - cacheAnalytics( - chartAnalyticsModel: newModel, - defaultSelected: defaultSelected, - selected: selected, - timeSpan: currentAnalyticsTimeSpan, - ); - - return newModel; - } catch (err, s) { - debugger(when: kDebugMode); - ErrorHandler.logError(e: err, s: s); - return ChartAnalyticsModel( - msgs: [], - timeSpan: currentAnalyticsTimeSpan, - ); - } - } - - //////////////////////////// CONSTRUCTS //////////////////////////// - - Future> allMyConstructs() async { - final Room? analyticsRoom = _pangeaController.matrixState.client - .analyticsRoomLocal(currentAnalyticsLang.langCode); + final Room? analyticsRoom = + _pangeaController.matrixState.client.analyticsRoomLocal(langCode); if (analyticsRoom == null) return []; final List? roomEvents = (await analyticsRoom.getAnalyticsEvents( - type: PangeaEventTypes.construct, - since: currentAnalyticsTimeSpan.cutOffDate, + since: timeSpan.cutOffDate, userId: _pangeaController.matrixState.client.userID!, )) ?.cast(); @@ -541,17 +171,17 @@ class AnalyticsController extends BaseController { Future> allSpaceMemberConstructs( Room space, + TimeSpan timeSpan, ) async { await space.requestParticipants(); final List constructEvents = []; for (final student in space.students) { final Room? analyticsRoom = _pangeaController.matrixState.client - .analyticsRoomLocal(currentAnalyticsLang.langCode, student.id); + .analyticsRoomLocal(langCode, student.id); if (analyticsRoom != null) { final List? roomEvents = (await analyticsRoom.getAnalyticsEvents( - type: PangeaEventTypes.construct, - since: currentAnalyticsTimeSpan.cutOffDate, + since: timeSpan.cutOffDate, userId: student.id, )) ?.cast(); @@ -600,8 +230,9 @@ class AnalyticsController extends BaseController { Room space, ) async { final List privateChatIds = space.allSpaceChildRoomIds; - final List lastFetched = await getLatestSpaceHierarchy(space.id); - for (final id in lastFetched) { + final resp = await space.client.getSpaceHierarchy(space.id); + final List chatIds = resp.rooms.map((room) => room.roomId).toList(); + for (final id in chatIds) { privateChatIds.removeWhere((e) => e == id); } final List filtered = @@ -618,7 +249,8 @@ class AnalyticsController extends BaseController { List unfilteredConstructs, Room space, ) async { - final List chatIds = await getLatestSpaceHierarchy(space.id); + final resp = await space.client.getSpaceHierarchy(space.id); + final List chatIds = resp.rooms.map((room) => room.roomId).toList(); final List filtered = List.from(unfilteredConstructs); @@ -633,10 +265,10 @@ class AnalyticsController extends BaseController { List? getConstructsLocal({ required TimeSpan timeSpan, - required ConstructTypeEnum constructType, required AnalyticsSelected defaultSelected, AnalyticsSelected? selected, DateTime? lastUpdated, + ConstructTypeEnum? constructType, }) { final index = _cachedConstructs.indexWhere( (e) => @@ -646,7 +278,7 @@ class AnalyticsController extends BaseController { e.defaultSelected.type == defaultSelected.type && e.selected?.id == selected?.id && e.selected?.type == selected?.type && - e.langCode == currentAnalyticsLang.langCode, + e.langCode == langCode, ); if (index > -1) { @@ -661,29 +293,31 @@ class AnalyticsController extends BaseController { } void cacheConstructs({ - required ConstructTypeEnum constructType, required List events, required AnalyticsSelected defaultSelected, + required TimeSpan timeSpan, AnalyticsSelected? selected, + ConstructTypeEnum? constructType, }) { final entry = ConstructCacheEntry( - timeSpan: currentAnalyticsTimeSpan, + timeSpan: timeSpan, type: constructType, events: List.from(events), defaultSelected: defaultSelected, selected: selected, - langCode: currentAnalyticsLang.langCode, + langCode: langCode, ); _cachedConstructs.add(entry); } Future> getMyConstructs({ required AnalyticsSelected defaultSelected, - required ConstructTypeEnum constructType, + required TimeSpan timeSpan, + ConstructTypeEnum? constructType, AnalyticsSelected? selected, }) async { final List unfilteredConstructs = - await allMyConstructs(); + await allMyConstructs(timeSpan); final Room? space = selected?.type == AnalyticsEntryType.space ? _pangeaController.matrixState.client.getRoomById(selected!.id) @@ -694,18 +328,21 @@ class AnalyticsController extends BaseController { space: space, defaultSelected: defaultSelected, selected: selected, + timeSpan: timeSpan, ); } Future> getSpaceConstructs({ - required ConstructTypeEnum constructType, required Room space, required AnalyticsSelected defaultSelected, + required TimeSpan timeSpan, AnalyticsSelected? selected, + ConstructTypeEnum? constructType, }) async { final List unfilteredConstructs = await allSpaceMemberConstructs( space, + timeSpan, ); return filterConstructs( @@ -713,12 +350,14 @@ class AnalyticsController extends BaseController { space: space, defaultSelected: defaultSelected, selected: selected, + timeSpan: timeSpan, ); } Future> filterConstructs({ required List unfilteredConstructs, required AnalyticsSelected defaultSelected, + required TimeSpan timeSpan, Room? space, AnalyticsSelected? selected, }) async { @@ -730,7 +369,7 @@ class AnalyticsController extends BaseController { for (int i = 0; i < unfilteredConstructs.length; i++) { final construct = unfilteredConstructs[i]; construct.content.uses.removeWhere( - (use) => use.timeStamp.isBefore(currentAnalyticsTimeSpan.cutOffDate), + (use) => use.timeStamp.isBefore(timeSpan.cutOffDate), ); } @@ -760,11 +399,12 @@ class AnalyticsController extends BaseController { } Future?> getConstructs({ - required ConstructTypeEnum constructType, required AnalyticsSelected defaultSelected, + required TimeSpan timeSpan, AnalyticsSelected? selected, bool removeIT = true, bool forceUpdate = false, + ConstructTypeEnum? constructType, }) async { debugPrint("getting constructs"); await _pangeaController.matrixState.client.roomsLoading; @@ -792,19 +432,16 @@ class AnalyticsController extends BaseController { // time the logged in user updated their analytics events // this gets passed to getAnalyticsLocal to determine if the cached // entry is out-of-date - lastUpdated = await myAnalyticsLastUpdated( - PangeaEventTypes.construct, - ); + lastUpdated = await myAnalyticsLastUpdated(); } else { // else, get the last time a student in the space updated their analytics lastUpdated = await spaceAnalyticsLastUpdated( - PangeaEventTypes.construct, space!, ); } final List? local = getConstructsLocal( - timeSpan: currentAnalyticsTimeSpan, + timeSpan: timeSpan, constructType: constructType, defaultSelected: defaultSelected, selected: selected, @@ -821,12 +458,14 @@ class AnalyticsController extends BaseController { constructType: constructType, defaultSelected: defaultSelected, selected: selected, + timeSpan: timeSpan, ) : await getSpaceConstructs( constructType: constructType, space: space, defaultSelected: defaultSelected, selected: selected, + timeSpan: timeSpan, ); if (removeIT) { @@ -846,6 +485,7 @@ class AnalyticsController extends BaseController { events: filteredConstructs, defaultSelected: defaultSelected, selected: selected, + timeSpan: timeSpan, ); } @@ -889,32 +529,15 @@ abstract class CacheEntry { } class ConstructCacheEntry extends CacheEntry { - final ConstructTypeEnum type; + final ConstructTypeEnum? type; final List events; ConstructCacheEntry({ - required this.type, required this.events, required super.timeSpan, required super.langCode, required super.defaultSelected, + this.type, super.selected, }); } - -class AnalyticsCacheModel extends CacheEntry { - final ChartAnalyticsModel chartAnalyticsModel; - - AnalyticsCacheModel({ - required this.chartAnalyticsModel, - required super.timeSpan, - required super.langCode, - required super.defaultSelected, - super.selected, - }); - - @override - bool get isExpired => - DateTime.now().difference(_createdAt).inMinutes > - ClassDefaultValues.minutesDelayToMakeNewChartAnalytics; -} diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 12baeb689..a13397e86 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -4,18 +4,16 @@ import 'dart:developer'; import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; +import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_record_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:matrix/matrix.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -import '../extensions/client_extension/client_extension.dart'; -import '../extensions/pangea_room_extension/pangea_room_extension.dart'; - /// handles the processing of analytics for /// 1) messages sent by the user and /// 2) constructs used by the user, both in sending messages and doing practice activities @@ -56,15 +54,14 @@ class MyAnalyticsController { /// If analytics haven't been updated in the last day, update them Future _refreshAnalyticsIfOutdated() async { - DateTime? lastUpdated = await _pangeaController.analytics - .myAnalyticsLastUpdated(PangeaEventTypes.summaryAnalytics); + DateTime? lastUpdated = + await _pangeaController.analytics.myAnalyticsLastUpdated(); final DateTime yesterday = DateTime.now().subtract(_timeSinceUpdate); if (lastUpdated?.isBefore(yesterday) ?? true) { debugPrint("analytics out-of-date, updating"); await updateAnalytics(); - lastUpdated = await _pangeaController.analytics - .myAnalyticsLastUpdated(PangeaEventTypes.summaryAnalytics); + lastUpdated = await _pangeaController.analytics.myAnalyticsLastUpdated(); } return lastUpdated; } @@ -238,7 +235,6 @@ class MyAnalyticsController { // get the last time analytics were updated for this room final DateTime? l2AnalyticsLastUpdated = await analyticsRoom.analyticsLastUpdated( - PangeaEventTypes.summaryAnalytics, _client.userID!, ); @@ -302,16 +298,6 @@ class MyAnalyticsController { final List allRecentMessages = recentPangeaMessageEvents.expand((e) => e).toList(); - final List summaryContent = - SummaryAnalyticsModel.formatSummaryContent(allRecentMessages); - // if there's new content to be sent, or if lastUpdated hasn't been - // set yet for this room, send the analytics events - if (summaryContent.isNotEmpty || l2AnalyticsLastUpdated == null) { - await analyticsRoom.sendSummaryAnalyticsEvent( - summaryContent, - ); - } - // get constructs for messages final List recentConstructUses = []; for (final PangeaMessageEvent message in allRecentMessages) { diff --git a/lib/pangea/enum/progress_indicators_enum.dart b/lib/pangea/enum/progress_indicators_enum.dart new file mode 100644 index 000000000..39032433e --- /dev/null +++ b/lib/pangea/enum/progress_indicators_enum.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +enum ProgressIndicatorEnum { + level, + wordsUsed, + errorTypes, +} + +extension ProgressIndicatorsExtension on ProgressIndicatorEnum { + IconData get icon { + switch (this) { + case ProgressIndicatorEnum.wordsUsed: + return Icons.text_fields_outlined; + case ProgressIndicatorEnum.errorTypes: + return Icons.error_outline; + case ProgressIndicatorEnum.level: + return Icons.star; + } + } + + static bool isDarkMode(BuildContext context) => + Theme.of(context).brightness == Brightness.dark; + + Color color(BuildContext context) { + switch (this) { + case ProgressIndicatorEnum.wordsUsed: + return Theme.of(context).brightness == Brightness.dark + ? const Color.fromARGB(255, 169, 183, 237) + : const Color.fromARGB(255, 38, 59, 141); + case ProgressIndicatorEnum.errorTypes: + return Theme.of(context).brightness == Brightness.dark + ? const Color.fromARGB(255, 212, 144, 216) + : const Color.fromARGB(255, 163, 39, 169); + case ProgressIndicatorEnum.level: + return Theme.of(context).brightness == Brightness.dark + ? const Color.fromARGB(255, 250, 220, 129) + : const Color.fromARGB(255, 255, 208, 67); + default: + return Theme.of(context).textTheme.bodyLarge!.color ?? Colors.blueGrey; + } + } + + String tooltip(BuildContext context) { + switch (this) { + case ProgressIndicatorEnum.wordsUsed: + return L10n.of(context)!.wordsUsed; + case ProgressIndicatorEnum.errorTypes: + return L10n.of(context)!.errorTypes; + case ProgressIndicatorEnum.level: + return L10n.of(context)!.level; + } + } +} diff --git a/lib/pangea/enum/time_span.dart b/lib/pangea/enum/time_span.dart index ddc9ce32b..4aaca7527 100644 --- a/lib/pangea/enum/time_span.dart +++ b/lib/pangea/enum/time_span.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import '../models/analytics/chart_analytics_model.dart'; - -enum TimeSpan { day, week, month, sixmonths, year } +enum TimeSpan { day, week, month, sixmonths, year, forever } extension TimeSpanFunctions on TimeSpan { String string(BuildContext context) { @@ -35,19 +33,8 @@ extension TimeSpanFunctions on TimeSpan { return 6; case TimeSpan.year: return 12; - } - } - - Duration timeAgo(int index) { - switch (this) { - case TimeSpan.day: - return Duration(hours: index); - case TimeSpan.week: - case TimeSpan.month: - return Duration(days: index); - case TimeSpan.year: - case TimeSpan.sixmonths: - return Duration(days: index * 32); + case TimeSpan.forever: + return 0; } } @@ -65,44 +52,8 @@ extension TimeSpanFunctions on TimeSpan { return DateTime.now().subtract(Duration(days: numberOfIntervals * 30)); case TimeSpan.year: return DateTime.now().subtract(const Duration(days: 365)); + case TimeSpan.forever: + return DateTime(2020); } } - - String getMapKey(DateTime date) { - switch (this) { - case TimeSpan.day: - return date.hour.toString(); - case TimeSpan.week: - return date.weekday.toString(); - case TimeSpan.month: - return date.day.toString(); - case TimeSpan.sixmonths: - case TimeSpan.year: - return date.month.toString(); - } - } - - /// Note: end is same as start!! - Map get emptyIntervals { - final DateTime now = DateTime.now(); - final List numbers = - List.generate(numberOfIntervals, (index) => index); - final Map map = {}; - - // debugger(when: kDebugMode); - for (final index in numbers) { - final timeAgos = timeAgo(index); - final DateTime end = now.subtract(timeAgos); - // debugger(when: end.isBefore(now.subtract(const Duration(days: 30)))); - final String mapKey = getMapKey(end); - // debugger(when: mapKey.toString() == "5"); - map[mapKey] = TimeSeriesInterval( - start: end, - end: end, - totals: TimeSeriesTotals.empty, - ); - } - // debugger(when: kDebugMode); - return map; - } } diff --git a/lib/pangea/extensions/client_extension/client_analytics_extension.dart b/lib/pangea/extensions/client_extension/client_analytics_extension.dart index 396e09f8d..2d2e19bc3 100644 --- a/lib/pangea/extensions/client_extension/client_analytics_extension.dart +++ b/lib/pangea/extensions/client_extension/client_analytics_extension.dart @@ -152,7 +152,6 @@ extension AnalyticsClientExtension on Client { final Map lastUpdatedMap = {}; for (final analyticsRoom in allMyAnalyticsRooms) { final DateTime? lastUpdated = await analyticsRoom.analyticsLastUpdated( - PangeaEventTypes.summaryAnalytics, userID!, ); lastUpdatedMap[analyticsRoom.id] = lastUpdated; diff --git a/lib/pangea/extensions/pangea_room_extension/events_extension.dart b/lib/pangea/extensions/pangea_room_extension/events_extension.dart index bc820998c..3fe14750d 100644 --- a/lib/pangea/extensions/pangea_room_extension/events_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/events_extension.dart @@ -282,83 +282,6 @@ extension EventsRoomExtension on Room { ); } - Future> get _messageListForAllChildChats async { - try { - if (!isSpace) return []; - final List spaceChats = spaceChildren - .where((e) => e.roomId != null) - .map((e) => client.getRoomById(e.roomId!)) - .where((element) => element != null) - .cast() - .where((element) => !element.isSpace) - .toList(); - - final List>> msgListFutures = []; - for (final chat in spaceChats) { - msgListFutures.add(chat._messageListForChat); - } - final List> msgLists = - await Future.wait(msgListFutures); - - final List joined = []; - for (final msgList in msgLists) { - joined.addAll(msgList); - } - return joined; - } catch (err) { - // debugger(when: kDebugMode); - rethrow; - } - } - - Future> get _messageListForChat async { - try { - int numberOfSearches = 0; - - if (isSpace) { - throw Exception( - "In messageListForChat with room that is not a chat", - ); - } - final Timeline timeline = await getTimeline(); - - while (timeline.canRequestHistory && numberOfSearches < 50) { - await timeline.requestHistory(historyCount: 100); - numberOfSearches += 1; - } - if (timeline.canRequestHistory) { - debugger(when: kDebugMode); - } - - final List msgs = []; - for (final event in timeline.events) { - if (event.senderId == client.userID && - event.type == EventTypes.Message && - event.content['msgtype'] == MessageTypes.Text) { - final PangeaMessageEvent pMsgEvent = PangeaMessageEvent( - event: event, - timeline: timeline, - ownMessage: true, - ); - msgs.add( - RecentMessageRecord( - eventId: event.eventId, - chatId: id, - useType: pMsgEvent.msgUseType, - time: event.originServerTs, - ), - ); - } - } - return msgs; - } catch (err, s) { - if (kDebugMode) rethrow; - debugger(when: kDebugMode); - ErrorHandler.logError(e: err, s: s); - return []; - } - } - // ConstructEvent? _vocabEventLocal(String lemma) { // if (!isAnalyticsRoom) throw Exception("not an analytics room"); @@ -451,14 +374,11 @@ extension EventsRoomExtension on Room { return false; } - while (timeline.canRequestHistory && - !reachedEnd() && - numberOfSearches < 10) { + while (timeline.canRequestHistory && numberOfSearches < 10) { await timeline.requestHistory(historyCount: 100); numberOfSearches += 1; - if (reachedEnd()) { - break; - } + if (!timeline.canRequestHistory) break; + if (reachedEnd()) break; } final List fetchedEvents = timeline.events diff --git a/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart index c22ba9a23..8bfb09c8a 100644 --- a/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart @@ -9,12 +9,8 @@ import 'package:fluffychat/pangea/constants/language_constants.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; -import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/models/analytics/analytics_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_event.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; import 'package:fluffychat/pangea/models/bot_options_model.dart'; import 'package:fluffychat/pangea/models/language_model.dart'; import 'package:fluffychat/pangea/models/space_model.dart'; @@ -80,22 +76,15 @@ extension PangeaRoom on Room { void inviteSpaceTeachersToAnalyticsRooms() => _inviteSpaceTeachersToAnalyticsRooms(); - Future getLastAnalyticsEvent( - String type, - String userId, - ) async => - await _getLastAnalyticsEvent(type, userId); - - Future analyticsLastUpdated(String type, String userId) async { - return await _analyticsLastUpdated(type, userId); + Future analyticsLastUpdated(String userId) async { + return await _analyticsLastUpdated(userId); } - Future?> getAnalyticsEvents({ - required String type, + Future?> getAnalyticsEvents({ required String userId, DateTime? since, }) async => - await _getAnalyticsEvents(type: type, since: since, userId: userId); + await _getAnalyticsEvents(since: since, userId: userId); String? get madeForLang => _madeForLang; diff --git a/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart b/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart index 7bdd13743..dabbd65bf 100644 --- a/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart @@ -140,52 +140,36 @@ extension AnalyticsRoomExtension on Room { ); } - Future _getLastAnalyticsEvent( - String type, + Future _getLastAnalyticsEvent( String userId, ) async { final List events = await getEventsBySender( - type: type, + type: PangeaEventTypes.construct, sender: userId, count: 10, ); if (events.isEmpty) return null; final Event event = events.first; - AnalyticsEvent? analyticsEvent; - switch (type) { - case PangeaEventTypes.summaryAnalytics: - analyticsEvent = SummaryAnalyticsEvent(event: event); - case PangeaEventTypes.construct: - analyticsEvent = ConstructAnalyticsEvent(event: event); - } - return analyticsEvent; + return ConstructAnalyticsEvent(event: event); } - Future _analyticsLastUpdated(String type, String userId) async { - final lastEvent = await _getLastAnalyticsEvent(type, userId); + Future _analyticsLastUpdated(String userId) async { + final lastEvent = await _getLastAnalyticsEvent(userId); return lastEvent?.event.originServerTs; } - Future?> _getAnalyticsEvents({ - required String type, + Future?> _getAnalyticsEvents({ required String userId, DateTime? since, }) async { final List events = await getEventsBySender( - type: type, + type: PangeaEventTypes.construct, sender: userId, since: since, ); - final List analyticsEvents = []; + final List analyticsEvents = []; for (final Event event in events) { - switch (type) { - case PangeaEventTypes.summaryAnalytics: - analyticsEvents.add(SummaryAnalyticsEvent(event: event)); - break; - case PangeaEventTypes.construct: - analyticsEvents.add(ConstructAnalyticsEvent(event: event)); - break; - } + analyticsEvents.add(ConstructAnalyticsEvent(event: event)); } return analyticsEvents; @@ -203,18 +187,6 @@ extension AnalyticsRoomExtension on Room { creationContent?.tryGet(ModelKey.oldLangCode) == langCode; } - Future sendSummaryAnalyticsEvent( - List records, - ) async { - final SummaryAnalyticsModel analyticsModel = SummaryAnalyticsModel( - messages: records, - ); - await sendEvent( - analyticsModel.toJson(), - type: PangeaEventTypes.summaryAnalytics, - ); - } - /// Sends construct events to the server. /// /// The [uses] parameter is a list of [OneConstructUse] objects representing the diff --git a/lib/pangea/models/analytics/analytics_event.dart b/lib/pangea/models/analytics/analytics_event.dart deleted file mode 100644 index 7010d3591..000000000 --- a/lib/pangea/models/analytics/analytics_event.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; -import 'package:fluffychat/pangea/models/analytics/analytics_model.dart'; -import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; -import 'package:matrix/matrix.dart'; - -// superclass for all analytics events -abstract class AnalyticsEvent { - late Event _event; - AnalyticsModel? contentCache; - - AnalyticsEvent({required Event event}) { - _event = event; - } - - Event get event => _event; - - AnalyticsModel get content { - switch (_event.type) { - case PangeaEventTypes.summaryAnalytics: - contentCache ??= SummaryAnalyticsModel.fromJson(event.content); - break; - case PangeaEventTypes.construct: - contentCache ??= ConstructAnalyticsModel.fromJson(event.content); - break; - } - return contentCache!; - } -} diff --git a/lib/pangea/models/analytics/analytics_model.dart b/lib/pangea/models/analytics/analytics_model.dart deleted file mode 100644 index d8732ad97..000000000 --- a/lib/pangea/models/analytics/analytics_model.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; -import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; - -abstract class AnalyticsModel { - static List formatAnalyticsContent( - List recentMsgs, - String type, - ) { - switch (type) { - case PangeaEventTypes.summaryAnalytics: - return SummaryAnalyticsModel.formatSummaryContent(recentMsgs); - case PangeaEventTypes.construct: - final List uses = []; - for (final msg in recentMsgs) { - uses.addAll(msg.allConstructUses); - } - return uses; - } - return []; - } -} diff --git a/lib/pangea/models/analytics/chart_analytics_model.dart b/lib/pangea/models/analytics/chart_analytics_model.dart index 7430ede2f..4a9f278be 100644 --- a/lib/pangea/models/analytics/chart_analytics_model.dart +++ b/lib/pangea/models/analytics/chart_analytics_model.dart @@ -1,149 +1,149 @@ -import 'dart:developer'; +// import 'dart:developer'; -import 'package:fluffychat/pangea/enum/time_span.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; -import 'package:flutter/foundation.dart'; +// import 'package:fluffychat/pangea/enum/time_span.dart'; +// import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; +// import 'package:flutter/foundation.dart'; -import '../../enum/use_type.dart'; +// import '../../enum/use_type.dart'; -class TimeSeriesTotals { - int ta; - int ga; - int wa; - int un; +// class TimeSeriesTotals { +// int ta; +// int ga; +// int wa; +// int un; - int get all => ta + ga + wa + un; +// int get all => ta + ga + wa + un; - TimeSeriesTotals({ - required this.ta, - required this.ga, - required this.wa, - required this.un, - }); +// TimeSeriesTotals({ +// required this.ta, +// required this.ga, +// required this.wa, +// required this.un, +// }); - Map toJson() => { - UseType.ta.string: ta, - UseType.ga.string: ga, - UseType.wa.string: wa, - UseType.un.string: un, - }; +// Map toJson() => { +// UseType.ta.string: ta, +// UseType.ga.string: ga, +// UseType.wa.string: wa, +// UseType.un.string: un, +// }; - factory TimeSeriesTotals.fromJson(json) => TimeSeriesTotals( - ta: json[UseType.ta.string], - ga: json[UseType.ga.string], - wa: json[UseType.wa.string], - un: json[UseType.un.string], - ); +// factory TimeSeriesTotals.fromJson(json) => TimeSeriesTotals( +// ta: json[UseType.ta.string], +// ga: json[UseType.ga.string], +// wa: json[UseType.wa.string], +// un: json[UseType.un.string], +// ); - static get empty => TimeSeriesTotals(ta: 0, ga: 0, wa: 0, un: 0); +// static get empty => TimeSeriesTotals(ta: 0, ga: 0, wa: 0, un: 0); - int get taPercent => all != 0 ? (ta / all * 100).round() : 0; - int get gaPercent => all != 0 ? (ga / all * 100).round() : 0; - int get waPercent => all != 0 ? (wa / all * 100).round() : 0; - int get unPercent => all != 0 ? (un / all * 100).round() : 0; +// int get taPercent => all != 0 ? (ta / all * 100).round() : 0; +// int get gaPercent => all != 0 ? (ga / all * 100).round() : 0; +// int get waPercent => all != 0 ? (wa / all * 100).round() : 0; +// int get unPercent => all != 0 ? (un / all * 100).round() : 0; - void increment(RecentMessageRecord msg) { - switch (msg.useType) { - case UseType.ta: - ta += 1; - break; - case UseType.wa: - wa += 1; - break; - case UseType.ga: - ga += 1; - break; - case UseType.un: - un += 1; - break; - default: - debugger(when: kDebugMode); - debugPrint("message with bad type ${msg.toJson()}"); - } - } -} +// void increment(RecentMessageRecord msg) { +// switch (msg.useType) { +// case UseType.ta: +// ta += 1; +// break; +// case UseType.wa: +// wa += 1; +// break; +// case UseType.ga: +// ga += 1; +// break; +// case UseType.un: +// un += 1; +// break; +// default: +// debugger(when: kDebugMode); +// debugPrint("message with bad type ${msg.toJson()}"); +// } +// } +// } -class TimeSeriesInterval { - DateTime start; - DateTime end; - TimeSeriesTotals totals; +// class TimeSeriesInterval { +// DateTime start; +// DateTime end; +// TimeSeriesTotals totals; - TimeSeriesInterval({ - required this.start, - required this.end, - required this.totals, - }); +// TimeSeriesInterval({ +// required this.start, +// required this.end, +// required this.totals, +// }); - Map toJson() => { - "strt": start.toIso8601String(), - "end": end.toIso8601String(), - "totals": totals.toJson(), - }; +// Map toJson() => { +// "strt": start.toIso8601String(), +// "end": end.toIso8601String(), +// "totals": totals.toJson(), +// }; - factory TimeSeriesInterval.fromJson(json) => TimeSeriesInterval( - start: DateTime.parse(json["strt"]), - end: DateTime.parse(json["end"]), - totals: TimeSeriesTotals.fromJson(json["totals"]), - ); -} +// factory TimeSeriesInterval.fromJson(json) => TimeSeriesInterval( +// start: DateTime.parse(json["strt"]), +// end: DateTime.parse(json["end"]), +// totals: TimeSeriesTotals.fromJson(json["totals"]), +// ); +// } -class ChartAnalyticsModel { - final TimeSpan timeSpan; - final TimeSeriesTotals totals = TimeSeriesTotals.empty; - final List msgs; - final String? chatId; +// class ChartAnalyticsModel { +// final TimeSpan timeSpan; +// final TimeSeriesTotals totals = TimeSeriesTotals.empty; +// final List msgs; +// final String? chatId; - late DateTime fetchedAt; - late List timeSeries; - DateTime? lastMessage; +// late DateTime fetchedAt; +// late List timeSeries; +// DateTime? lastMessage; - ChartAnalyticsModel({ - required this.timeSpan, - required this.msgs, - this.chatId, - }) { - fetchedAt = DateTime.now(); - calculate(); - } +// ChartAnalyticsModel({ +// required this.timeSpan, +// required this.msgs, +// this.chatId, +// }) { +// fetchedAt = DateTime.now(); +// calculate(); +// } - bool get isEmpty => (totals.ga + totals.ta + totals.wa == 0); +// bool get isEmpty => (totals.ga + totals.ta + totals.wa == 0); - void calculate() { - final Map intervals = timeSpan.emptyIntervals; - final DateTime cutOff = timeSpan.cutOffDate; +// void calculate() { +// final Map intervals = timeSpan.emptyIntervals; +// final DateTime cutOff = timeSpan.cutOffDate; - final filtered = msgs.where( - (msg) => - (chatId == null || msg.chatId == chatId) && msg.time.isAfter(cutOff), - ); +// final filtered = msgs.where( +// (msg) => +// (chatId == null || msg.chatId == chatId) && msg.time.isAfter(cutOff), +// ); - //remove msgs with duplicate ids - final Map unique = {}; - for (final msg in filtered) { - if (unique[msg.eventId] == null) { - unique[msg.eventId] = msg; - } - } +// //remove msgs with duplicate ids +// final Map unique = {}; +// for (final msg in filtered) { +// if (unique[msg.eventId] == null) { +// unique[msg.eventId] = msg; +// } +// } - for (final msg in unique.values) { - final String key = timeSpan.getMapKey(msg.time); - if (intervals[key] == null) { - debugger(when: kDebugMode); - } else { - intervals[key]!.totals.increment(msg); - totals.increment(msg); - lastMessage = msg.time; - } - } - timeSeries = intervals.values.toList().reversed.toList(); - } +// for (final msg in unique.values) { +// final String key = timeSpan.getMapKey(msg.time); +// if (intervals[key] == null) { +// debugger(when: kDebugMode); +// } else { +// intervals[key]!.totals.increment(msg); +// totals.increment(msg); +// lastMessage = msg.time; +// } +// } +// timeSeries = intervals.values.toList().reversed.toList(); +// } - DateTime? get lastMessageTime { - if (msgs.isEmpty) { - return null; - } - return msgs.map((msg) => msg.time).reduce( - (compare, recent) => compare.isAfter(recent) ? compare : recent, - ); - } -} +// DateTime? get lastMessageTime { +// if (msgs.isEmpty) { +// return null; +// } +// return msgs.map((msg) => msg.time).reduce( +// (compare, recent) => compare.isAfter(recent) ? compare : recent, +// ); +// } +// } diff --git a/lib/pangea/models/analytics/constructs_event.dart b/lib/pangea/models/analytics/constructs_event.dart index 481051b1c..89336bbbc 100644 --- a/lib/pangea/models/analytics/constructs_event.dart +++ b/lib/pangea/models/analytics/constructs_event.dart @@ -1,11 +1,13 @@ -import 'package:fluffychat/pangea/models/analytics/analytics_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:matrix/matrix.dart'; import '../../constants/pangea_event_types.dart'; -class ConstructAnalyticsEvent extends AnalyticsEvent { - ConstructAnalyticsEvent({required Event event}) : super(event: event) { +class ConstructAnalyticsEvent { + late Event _event; + ConstructAnalyticsModel? contentCache; + ConstructAnalyticsEvent({required Event event}) { + _event = event; if (event.type != PangeaEventTypes.construct) { throw Exception( "${event.type} should not be used to make a ConstructAnalyticsEvent", @@ -13,7 +15,8 @@ class ConstructAnalyticsEvent extends AnalyticsEvent { } } - @override + Event get event => _event; + ConstructAnalyticsModel get content { contentCache ??= ConstructAnalyticsModel.fromJson(event.content); return contentCache as ConstructAnalyticsModel; diff --git a/lib/pangea/models/analytics/constructs_model.dart b/lib/pangea/models/analytics/constructs_model.dart index 54e81789f..ff6b55aef 100644 --- a/lib/pangea/models/analytics/constructs_model.dart +++ b/lib/pangea/models/analytics/constructs_model.dart @@ -1,14 +1,13 @@ import 'dart:developer'; import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; -import 'package:fluffychat/pangea/models/analytics/analytics_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:matrix/matrix.dart'; import '../../enum/construct_type_enum.dart'; -class ConstructAnalyticsModel extends AnalyticsModel { +class ConstructAnalyticsModel { List uses; ConstructAnalyticsModel({ diff --git a/lib/pangea/models/analytics/summary_analytics_event.dart b/lib/pangea/models/analytics/summary_analytics_event.dart deleted file mode 100644 index a764d5597..000000000 --- a/lib/pangea/models/analytics/summary_analytics_event.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:fluffychat/pangea/models/analytics/analytics_event.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; -import 'package:matrix/matrix.dart'; - -import '../../constants/pangea_event_types.dart'; - -class SummaryAnalyticsEvent extends AnalyticsEvent { - SummaryAnalyticsEvent({required Event event}) : super(event: event) { - if (event.type != PangeaEventTypes.summaryAnalytics) { - throw Exception( - "${event.type} should not be used to make a SummaryAnalyticsEvent", - ); - } - } - - @override - SummaryAnalyticsModel get content { - contentCache ??= SummaryAnalyticsModel.fromJson(event.content); - return contentCache as SummaryAnalyticsModel; - } -} diff --git a/lib/pangea/models/analytics/summary_analytics_model.dart b/lib/pangea/models/analytics/summary_analytics_model.dart deleted file mode 100644 index 0b8e4b27c..000000000 --- a/lib/pangea/models/analytics/summary_analytics_model.dart +++ /dev/null @@ -1,111 +0,0 @@ -import 'dart:convert'; - -import 'package:fluffychat/pangea/enum/use_type.dart'; -import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/models/analytics/analytics_model.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:flutter/foundation.dart'; - -class SummaryAnalyticsModel extends AnalyticsModel { - late List _messages; - - SummaryAnalyticsModel({ - required List messages, - }) { - _messages = messages; - } - - List get messages => _messages; - - static const _messagesKey = "msgs"; - - Map toJson() => { - _messagesKey: jsonEncode(_messages.map((e) => e.toJson()).toList()), - }; - - factory SummaryAnalyticsModel.fromJson(json) { - List savedMessages = []; - try { - savedMessages = json[_messagesKey] != null - ? (jsonDecode(json[_messagesKey] ?? "[]") as Iterable) - .map((e) => RecentMessageRecord.fromJson(e)) - .toList() - .cast() - : []; - } catch (err, stack) { - if (kDebugMode) rethrow; - ErrorHandler.logError(e: err, s: stack); - } - return SummaryAnalyticsModel( - messages: savedMessages, - ); - } - - static List formatSummaryContent( - List recentMsgs, - ) { - final List filtered = List.from(recentMsgs); - final List records = filtered - .map( - (msg) => RecentMessageRecord( - eventId: msg.eventId, - chatId: msg.room.id, - useType: msg.msgUseType, - time: msg.originServerTs, - ), - ) - .toList(); - - return records; - } -} - -class RecentMessageRecord { - String eventId; - String chatId; - UseType useType; - DateTime time; - - RecentMessageRecord({ - required this.eventId, - required this.chatId, - required this.useType, - required this.time, - }); - - factory RecentMessageRecord.fromJson(Map json) => - RecentMessageRecord( - eventId: json[_eventIdKey], - chatId: json[_chatIdKey], - useType: _typeStringToEnum(json[_typeOfUseKey]), - time: DateTime.parse(json[_timeKey]), - ); - - Map toJson() => { - _eventIdKey: eventId, - _chatIdKey: chatId, - _typeOfUseKey: _typeEnumToString(useType), - _timeKey: time.toIso8601String(), - }; - - String _typeEnumToString(dynamic status) => status.toString().split('.').last; - - static UseType _typeStringToEnum(String useType) { - final String lastPart = useType.toString().split('.').last; - switch (lastPart) { - case 'ta': - return UseType.ta; - case 'ga': - return UseType.ga; - case 'wa': - return UseType.wa; - default: - return UseType.un; - } - } - - static const _eventIdKey = "m.id"; - static const _chatIdKey = "c.id"; - static const _typeOfUseKey = "typ"; - static const _timeKey = "t"; -} diff --git a/lib/pangea/pages/analytics/analytics_list_tile.dart b/lib/pangea/pages/analytics/analytics_list_tile.dart index a49ef4cb4..1f26dada9 100644 --- a/lib/pangea/pages/analytics/analytics_list_tile.dart +++ b/lib/pangea/pages/analytics/analytics_list_tile.dart @@ -1,156 +1,156 @@ -import 'dart:async'; +// import 'dart:async'; -import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; -import 'package:matrix/matrix.dart'; +// import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +// import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_gen/gen_l10n/l10n.dart'; +// import 'package:go_router/go_router.dart'; +// import 'package:matrix/matrix.dart'; -import '../../../../utils/date_time_extension.dart'; -import '../../../widgets/avatar.dart'; -import '../../../widgets/matrix.dart'; -import '../../models/analytics/chart_analytics_model.dart'; -import 'base_analytics.dart'; -import 'list_summary_analytics.dart'; +// import '../../../../utils/date_time_extension.dart'; +// import '../../../widgets/avatar.dart'; +// import '../../../widgets/matrix.dart'; +// import '../../models/analytics/chart_analytics_model.dart'; +// import 'base_analytics.dart'; +// import 'list_summary_analytics.dart'; -class AnalyticsListTile extends StatefulWidget { - const AnalyticsListTile({ - super.key, - required this.defaultSelected, - required this.selected, - required this.avatar, - required this.allowNavigateOnSelect, - required this.isSelected, - required this.onTap, - required this.pangeaController, - this.controller, - this.refreshStream, - }); +// class AnalyticsListTile extends StatefulWidget { +// const AnalyticsListTile({ +// super.key, +// required this.defaultSelected, +// required this.selected, +// required this.avatar, +// required this.allowNavigateOnSelect, +// required this.isSelected, +// required this.onTap, +// required this.pangeaController, +// this.controller, +// this.refreshStream, +// }); - final void Function(AnalyticsSelected) onTap; +// final void Function(AnalyticsSelected) onTap; - final AnalyticsSelected defaultSelected; - final AnalyticsSelected selected; +// final AnalyticsSelected defaultSelected; +// final AnalyticsSelected selected; - final Uri? avatar; +// final Uri? avatar; - final bool allowNavigateOnSelect; - final bool isSelected; +// final bool allowNavigateOnSelect; +// final bool isSelected; - final PangeaController pangeaController; - final BaseAnalyticsController? controller; - final StreamController? refreshStream; +// final PangeaController pangeaController; +// final BaseAnalyticsController? controller; +// final StreamController? refreshStream; - @override - AnalyticsListTileState createState() => AnalyticsListTileState(); -} +// @override +// AnalyticsListTileState createState() => AnalyticsListTileState(); +// } -class AnalyticsListTileState extends State { - ChartAnalyticsModel? tileData; - StreamSubscription? refreshSubscription; +// class AnalyticsListTileState extends State { +// ChartAnalyticsModel? tileData; +// StreamSubscription? refreshSubscription; - @override - void initState() { - super.initState(); - setTileData(); - refreshSubscription = widget.refreshStream?.stream.listen((forceUpdate) { - setTileData(forceUpdate: forceUpdate); - }); - } +// @override +// void initState() { +// super.initState(); +// setTileData(); +// refreshSubscription = widget.refreshStream?.stream.listen((forceUpdate) { +// setTileData(forceUpdate: forceUpdate); +// }); +// } - @override - void didUpdateWidget(covariant AnalyticsListTile oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.selected != widget.selected) { - setTileData(); - } - } +// @override +// void didUpdateWidget(covariant AnalyticsListTile oldWidget) { +// super.didUpdateWidget(oldWidget); +// if (oldWidget.selected != widget.selected) { +// setTileData(); +// } +// } - @override - void dispose() { - refreshSubscription?.cancel(); - super.dispose(); - } +// @override +// void dispose() { +// refreshSubscription?.cancel(); +// super.dispose(); +// } - Future setTileData({forceUpdate = false}) async { - tileData = await MatrixState.pangeaController.analytics.getAnalytics( - defaultSelected: widget.defaultSelected, - selected: widget.selected, - forceUpdate: forceUpdate, - ); - if (mounted) setState(() {}); - } +// Future setTileData({forceUpdate = false}) async { +// tileData = await MatrixState.pangeaController.analytics.getAnalytics( +// defaultSelected: widget.defaultSelected, +// selected: widget.selected, +// forceUpdate: forceUpdate, +// ); +// if (mounted) setState(() {}); +// } - @override - Widget build(BuildContext context) { - final Room? room = - Matrix.of(context).client.getRoomById(widget.selected.id); - return Material( - color: widget.isSelected - ? Theme.of(context).colorScheme.secondaryContainer - : Colors.transparent, - child: ListTile( - leading: widget.selected.type == AnalyticsEntryType.privateChats - ? CircleAvatar( - backgroundColor: Theme.of(context).primaryColor, - foregroundColor: Colors.white, - radius: Avatar.defaultSize / 2, - child: const Icon(Icons.forum), - ) - : Avatar( - mxContent: widget.avatar, - name: widget.selected.displayName, - littleIcon: room?.roomTypeIcon, - ), - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - widget.selected.displayName, - maxLines: 1, - overflow: TextOverflow.ellipsis, - softWrap: false, - style: TextStyle( - fontWeight: FontWeight.bold, - color: Theme.of(context).textTheme.bodyLarge!.color, - ), - ), - ), - Tooltip( - message: L10n.of(context)!.timeOfLastMessage, - child: Text( - tileData?.lastMessageTime?.localizedTimeShort(context) ?? "", - style: TextStyle( - fontSize: 13, - color: Theme.of(context).textTheme.bodyMedium!.color, - ), - ), - ), - ], - ), - subtitle: ListSummaryAnalytics( - chartAnalytics: tileData, - ), - selected: widget.isSelected, - onTap: () { - if (widget.controller?.widget.selectedView == null) { - widget.onTap(widget.selected); - return; - } - if ((room?.isSpace ?? false) && widget.allowNavigateOnSelect) { - context.go('/rooms/analytics/${room!.id}'); - return; - } - widget.onTap(widget.selected); - }, - trailing: (room?.isSpace ?? false) && - widget.selected.type != AnalyticsEntryType.privateChats && - widget.allowNavigateOnSelect - ? const Icon(Icons.chevron_right) - : null, - ), - ); - } -} +// @override +// Widget build(BuildContext context) { +// final Room? room = +// Matrix.of(context).client.getRoomById(widget.selected.id); +// return Material( +// color: widget.isSelected +// ? Theme.of(context).colorScheme.secondaryContainer +// : Colors.transparent, +// child: ListTile( +// leading: widget.selected.type == AnalyticsEntryType.privateChats +// ? CircleAvatar( +// backgroundColor: Theme.of(context).primaryColor, +// foregroundColor: Colors.white, +// radius: Avatar.defaultSize / 2, +// child: const Icon(Icons.forum), +// ) +// : Avatar( +// mxContent: widget.avatar, +// name: widget.selected.displayName, +// littleIcon: room?.roomTypeIcon, +// ), +// title: Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// Expanded( +// child: Text( +// widget.selected.displayName, +// maxLines: 1, +// overflow: TextOverflow.ellipsis, +// softWrap: false, +// style: TextStyle( +// fontWeight: FontWeight.bold, +// color: Theme.of(context).textTheme.bodyLarge!.color, +// ), +// ), +// ), +// Tooltip( +// message: L10n.of(context)!.timeOfLastMessage, +// child: Text( +// tileData?.lastMessageTime?.localizedTimeShort(context) ?? "", +// style: TextStyle( +// fontSize: 13, +// color: Theme.of(context).textTheme.bodyMedium!.color, +// ), +// ), +// ), +// ], +// ), +// subtitle: ListSummaryAnalytics( +// chartAnalytics: tileData, +// ), +// selected: widget.isSelected, +// onTap: () { +// if (widget.controller?.widget.selectedView == null) { +// widget.onTap(widget.selected); +// return; +// } +// if ((room?.isSpace ?? false) && widget.allowNavigateOnSelect) { +// context.go('/rooms/analytics/${room!.id}'); +// return; +// } +// widget.onTap(widget.selected); +// }, +// trailing: (room?.isSpace ?? false) && +// widget.selected.type != AnalyticsEntryType.privateChats && +// widget.allowNavigateOnSelect +// ? const Icon(Icons.chevron_right) +// : null, +// ), +// ); +// } +// } diff --git a/lib/pangea/pages/analytics/base_analytics.dart b/lib/pangea/pages/analytics/base_analytics.dart index cd41d2312..408505987 100644 --- a/lib/pangea/pages/analytics/base_analytics.dart +++ b/lib/pangea/pages/analytics/base_analytics.dart @@ -1,214 +1,214 @@ -import 'dart:async'; +// import 'dart:async'; -import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; -import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/models/analytics/analytics_event.dart'; -import 'package:fluffychat/pangea/models/language_model.dart'; -import 'package:fluffychat/pangea/pages/analytics/base_analytics_view.dart'; -import 'package:fluffychat/pangea/pages/analytics/student_analytics/student_analytics.dart'; -import 'package:flutter/material.dart'; -import 'package:future_loading_dialog/future_loading_dialog.dart'; -import 'package:matrix/matrix.dart'; +// import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; +// import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; +// import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; +// import 'package:fluffychat/pangea/models/analytics/analytics_event.dart'; +// import 'package:fluffychat/pangea/models/language_model.dart'; +// import 'package:fluffychat/pangea/pages/analytics/base_analytics_view.dart'; +// import 'package:fluffychat/pangea/pages/analytics/student_analytics/student_analytics.dart'; +// import 'package:flutter/material.dart'; +// import 'package:future_loading_dialog/future_loading_dialog.dart'; +// import 'package:matrix/matrix.dart'; -import '../../../widgets/matrix.dart'; -import '../../controllers/pangea_controller.dart'; -import '../../enum/bar_chart_view_enum.dart'; -import '../../enum/time_span.dart'; -import '../../models/analytics/chart_analytics_model.dart'; +// import '../../../widgets/matrix.dart'; +// import '../../controllers/pangea_controller.dart'; +// import '../../enum/bar_chart_view_enum.dart'; +// import '../../enum/time_span.dart'; +// import '../../models/analytics/chart_analytics_model.dart'; -class BaseAnalyticsPage extends StatefulWidget { - final String pageTitle; - final List tabs; - final BarChartViewSelection selectedView; +// class BaseAnalyticsPage extends StatefulWidget { +// final String pageTitle; +// final List tabs; +// final BarChartViewSelection selectedView; - final AnalyticsSelected defaultSelected; - final AnalyticsSelected? alwaysSelected; - final StudentAnalyticsController? myAnalyticsController; - final List targetLanguages; +// final AnalyticsSelected defaultSelected; +// final AnalyticsSelected? alwaysSelected; +// final StudentAnalyticsController? myAnalyticsController; +// final List targetLanguages; - BaseAnalyticsPage({ - super.key, - required this.pageTitle, - required this.tabs, - required this.alwaysSelected, - required this.defaultSelected, - required this.selectedView, - this.myAnalyticsController, - targetLanguages, - }) : targetLanguages = (targetLanguages?.isNotEmpty ?? false) - ? targetLanguages - : MatrixState.pangeaController.pLanguageStore.targetOptions; +// BaseAnalyticsPage({ +// super.key, +// required this.pageTitle, +// required this.tabs, +// required this.alwaysSelected, +// required this.defaultSelected, +// required this.selectedView, +// this.myAnalyticsController, +// targetLanguages, +// }) : targetLanguages = (targetLanguages?.isNotEmpty ?? false) +// ? targetLanguages +// : MatrixState.pangeaController.pLanguageStore.targetOptions; - @override - State createState() => BaseAnalyticsController(); -} +// @override +// State createState() => BaseAnalyticsController(); +// } -class BaseAnalyticsController extends State { - final PangeaController pangeaController = MatrixState.pangeaController; - AnalyticsSelected? selected; - String? currentLemma; - ChartAnalyticsModel? chartData; - StreamController refreshStream = StreamController.broadcast(); - BarChartViewSelection currentView = BarChartViewSelection.messages; +// class BaseAnalyticsController extends State { +// final PangeaController pangeaController = MatrixState.pangeaController; +// AnalyticsSelected? selected; +// String? currentLemma; +// ChartAnalyticsModel? chartData; +// StreamController refreshStream = StreamController.broadcast(); +// BarChartViewSelection currentView = BarChartViewSelection.messages; - bool isSelected(String chatOrStudentId) => chatOrStudentId == selected?.id; +// bool isSelected(String chatOrStudentId) => chatOrStudentId == selected?.id; - Room? get activeSpace { - if (widget.defaultSelected.type == AnalyticsEntryType.space) { - return Matrix.of(context).client.getRoomById(widget.defaultSelected.id); - } - return null; - } +// Room? get activeSpace { +// if (widget.defaultSelected.type == AnalyticsEntryType.space) { +// return Matrix.of(context).client.getRoomById(widget.defaultSelected.id); +// } +// return null; +// } - @override - void initState() { - super.initState(); - currentView = widget.selectedView; - if (widget.defaultSelected.type == AnalyticsEntryType.student) { - runFirstRefresh(); - } - setChartData(); - } +// @override +// void initState() { +// super.initState(); +// currentView = widget.selectedView; +// if (widget.defaultSelected.type == AnalyticsEntryType.student) { +// runFirstRefresh(); +// } +// setChartData(); +// } - @override - void didUpdateWidget(covariant BaseAnalyticsPage oldWidget) { - // when a user is a parent space's analytics and clicks on a subspace - super.didUpdateWidget(oldWidget); - if (oldWidget.defaultSelected.id != widget.defaultSelected.id) { - setChartData(); - refreshStream.add(false); - } - } +// @override +// void didUpdateWidget(covariant BaseAnalyticsPage oldWidget) { +// // when a user is a parent space's analytics and clicks on a subspace +// super.didUpdateWidget(oldWidget); +// if (oldWidget.defaultSelected.id != widget.defaultSelected.id) { +// setChartData(); +// refreshStream.add(false); +// } +// } - Future runFirstRefresh() async { - final analyticsRooms = - pangeaController.matrixState.client.allMyAnalyticsRooms; +// Future runFirstRefresh() async { +// final analyticsRooms = +// pangeaController.matrixState.client.allMyAnalyticsRooms; - final List analyticsEvent = []; - for (final analyticsRoom in analyticsRooms) { - final lastSummaryEvent = await analyticsRoom.getLastAnalyticsEvent( - PangeaEventTypes.summaryAnalytics, - Matrix.of(context).client.userID!, - ); - final lastConstructEvent = await analyticsRoom.getLastAnalyticsEvent( - PangeaEventTypes.construct, - Matrix.of(context).client.userID!, - ); - if (lastSummaryEvent != null) { - analyticsEvent.add(lastSummaryEvent); - } - if (lastConstructEvent != null) { - analyticsEvent.add(lastConstructEvent); - } - } +// final List analyticsEvent = []; +// for (final analyticsRoom in analyticsRooms) { +// final lastSummaryEvent = await analyticsRoom.getLastAnalyticsEvent( +// PangeaEventTypes.summaryAnalytics, +// Matrix.of(context).client.userID!, +// ); +// final lastConstructEvent = await analyticsRoom.getLastAnalyticsEvent( +// PangeaEventTypes.construct, +// Matrix.of(context).client.userID!, +// ); +// if (lastSummaryEvent != null) { +// analyticsEvent.add(lastSummaryEvent); +// } +// if (lastConstructEvent != null) { +// analyticsEvent.add(lastConstructEvent); +// } +// } - if (analyticsEvent.isNotEmpty) return; - onRefresh(); - } +// if (analyticsEvent.isNotEmpty) return; +// onRefresh(); +// } - Future onRefresh() async { - // postframe callback to avoid calling this function during build - WidgetsBinding.instance.addPostFrameCallback((_) async { - await showFutureLoadingDialog( - context: context, - future: () async { - debugPrint("updating analytics"); - await pangeaController.myAnalytics.updateAnalytics(); - await setChartData(forceUpdate: true); - refreshStream.add(true); - }, - ); - }); - } +// Future onRefresh() async { +// // postframe callback to avoid calling this function during build +// WidgetsBinding.instance.addPostFrameCallback((_) async { +// await showFutureLoadingDialog( +// context: context, +// future: () async { +// debugPrint("updating analytics"); +// await pangeaController.myAnalytics.updateAnalytics(); +// await setChartData(forceUpdate: true); +// refreshStream.add(true); +// }, +// ); +// }); +// } - Future fetchChartData( - AnalyticsSelected? params, { - forceUpdate = false, - }) async { - final ChartAnalyticsModel data = - await pangeaController.analytics.getAnalytics( - defaultSelected: widget.defaultSelected, - selected: params, - forceUpdate: forceUpdate, - ); +// Future fetchChartData( +// AnalyticsSelected? params, { +// forceUpdate = false, +// }) async { +// final ChartAnalyticsModel data = +// await pangeaController.analytics.getAnalytics( +// defaultSelected: widget.defaultSelected, +// selected: params, +// forceUpdate: forceUpdate, +// ); - return data; - } +// return data; +// } - Future setChartData({forceUpdate = false}) async { - final ChartAnalyticsModel newData = await fetchChartData( - selected, - forceUpdate: forceUpdate, - ); - setState(() => chartData = newData); - } +// Future setChartData({forceUpdate = false}) async { +// final ChartAnalyticsModel newData = await fetchChartData( +// selected, +// forceUpdate: forceUpdate, +// ); +// setState(() => chartData = newData); +// } - TimeSpan get currentTimeSpan => - pangeaController.analytics.currentAnalyticsTimeSpan; +// TimeSpan get currentTimeSpan => +// pangeaController.analytics.currentAnalyticsTimeSpan; - Future toggleSelection(AnalyticsSelected selectedParam) async { - setState(() { - debugPrint("selectedParam.id is ${selectedParam.id}"); - currentLemma = null; - selected = isSelected(selectedParam.id) ? null : selectedParam; - }); - await setChartData(); - refreshStream.add(false); - Future.delayed(Duration.zero, () => setState(() {})); - } +// Future toggleSelection(AnalyticsSelected selectedParam) async { +// setState(() { +// debugPrint("selectedParam.id is ${selectedParam.id}"); +// currentLemma = null; +// selected = isSelected(selectedParam.id) ? null : selectedParam; +// }); +// await setChartData(); +// refreshStream.add(false); +// Future.delayed(Duration.zero, () => setState(() {})); +// } - Future toggleTimeSpan(BuildContext context, TimeSpan timeSpan) async { - await pangeaController.analytics.setCurrentAnalyticsTimeSpan(timeSpan); - await setChartData(); - refreshStream.add(false); - } +// Future toggleTimeSpan(BuildContext context, TimeSpan timeSpan) async { +// await pangeaController.analytics.setCurrentAnalyticsTimeSpan(timeSpan); +// await setChartData(); +// refreshStream.add(false); +// } - Future toggleSpaceLang(LanguageModel lang) async { - await pangeaController.analytics.setCurrentAnalyticsLang(lang); - await setChartData(); - refreshStream.add(false); - } +// Future toggleSpaceLang(LanguageModel lang) async { +// await pangeaController.analytics.setCurrentAnalyticsLang(lang); +// await setChartData(); +// refreshStream.add(false); +// } - Future toggleView(BarChartViewSelection view) async { - currentView = view; - await setChartData(); - refreshStream.add(false); - } +// Future toggleView(BarChartViewSelection view) async { +// currentView = view; +// await setChartData(); +// refreshStream.add(false); +// } - void setCurrentLemma(String? lemma) { - currentLemma = lemma; - setState(() {}); - refreshStream.add(false); - } +// void setCurrentLemma(String? lemma) { +// currentLemma = lemma; +// setState(() {}); +// refreshStream.add(false); +// } - @override - Widget build(BuildContext context) { - return BaseAnalyticsView(controller: this); - } -} +// @override +// Widget build(BuildContext context) { +// return BaseAnalyticsView(controller: this); +// } +// } -class TabData { - AnalyticsEntryType type; - IconData icon; - List items; - bool allowNavigateOnSelect; +// class TabData { +// AnalyticsEntryType type; +// IconData icon; +// List items; +// bool allowNavigateOnSelect; - TabData({ - required this.type, - required this.items, - required this.icon, - this.allowNavigateOnSelect = true, - }); -} +// TabData({ +// required this.type, +// required this.items, +// required this.icon, +// this.allowNavigateOnSelect = true, +// }); +// } -class TabItem { - Uri? avatar; - String displayName; - String id; +// class TabItem { +// Uri? avatar; +// String displayName; +// String id; - TabItem({required this.avatar, required this.displayName, required this.id}); -} +// TabItem({required this.avatar, required this.displayName, required this.id}); +// } enum AnalyticsEntryType { student, room, space, privateChats } diff --git a/lib/pangea/pages/analytics/base_analytics_view.dart b/lib/pangea/pages/analytics/base_analytics_view.dart index 13e49aa38..60e31c5c8 100644 --- a/lib/pangea/pages/analytics/base_analytics_view.dart +++ b/lib/pangea/pages/analytics/base_analytics_view.dart @@ -1,243 +1,243 @@ -import 'dart:math'; +// import 'dart:math'; -import 'package:fluffychat/pangea/enum/bar_chart_view_enum.dart'; -import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; -import 'package:fluffychat/pangea/enum/time_span.dart'; -import 'package:fluffychat/pangea/pages/analytics/analytics_language_button.dart'; -import 'package:fluffychat/pangea/pages/analytics/analytics_list_tile.dart'; -import 'package:fluffychat/pangea/pages/analytics/analytics_view_button.dart'; -import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart'; -import 'package:fluffychat/pangea/pages/analytics/construct_list.dart'; -import 'package:fluffychat/pangea/pages/analytics/messages_bar_chart.dart'; -import 'package:fluffychat/pangea/pages/analytics/time_span_menu_button.dart'; -import 'package:fluffychat/widgets/layouts/max_width_body.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; +// import 'package:fluffychat/pangea/enum/bar_chart_view_enum.dart'; +// import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; +// import 'package:fluffychat/pangea/enum/time_span.dart'; +// import 'package:fluffychat/pangea/pages/analytics/analytics_language_button.dart'; +// import 'package:fluffychat/pangea/pages/analytics/analytics_list_tile.dart'; +// import 'package:fluffychat/pangea/pages/analytics/analytics_view_button.dart'; +// import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart'; +// import 'package:fluffychat/pangea/pages/analytics/construct_list.dart'; +// import 'package:fluffychat/pangea/pages/analytics/messages_bar_chart.dart'; +// import 'package:fluffychat/pangea/pages/analytics/time_span_menu_button.dart'; +// import 'package:fluffychat/widgets/layouts/max_width_body.dart'; +// import 'package:flutter/gestures.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_gen/gen_l10n/l10n.dart'; +// import 'package:go_router/go_router.dart'; -class BaseAnalyticsView extends StatelessWidget { - const BaseAnalyticsView({ - super.key, - required this.controller, - }); +// class BaseAnalyticsView extends StatelessWidget { +// const BaseAnalyticsView({ +// super.key, +// required this.controller, +// }); - final BaseAnalyticsController controller; +// final BaseAnalyticsController controller; - Widget chartView(BuildContext context) { - switch (controller.currentView) { - case BarChartViewSelection.messages: - return MessagesBarChart( - chartAnalytics: controller.chartData, - ); - case BarChartViewSelection.grammar: - return ConstructList( - constructType: ConstructTypeEnum.grammar, - defaultSelected: controller.widget.defaultSelected, - selected: controller.selected, - controller: controller, - pangeaController: controller.pangeaController, - refreshStream: controller.refreshStream, - ); - } - } +// Widget chartView(BuildContext context) { +// switch (controller.currentView) { +// case BarChartViewSelection.messages: +// return MessagesBarChart( +// chartAnalytics: controller.chartData, +// ); +// case BarChartViewSelection.grammar: +// return ConstructList( +// constructType: ConstructTypeEnum.grammar, +// defaultSelected: controller.widget.defaultSelected, +// selected: controller.selected, +// controller: controller, +// pangeaController: controller.pangeaController, +// refreshStream: controller.refreshStream, +// ); +// } +// } - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - centerTitle: true, - title: RichText( - text: TextSpan( - style: TextStyle( - color: Theme.of(context).textTheme.bodyLarge!.color, - fontSize: 18, - fontWeight: FontWeight.w700, - ), - children: [ - TextSpan( - text: controller.widget.pageTitle, - style: const TextStyle(decoration: TextDecoration.underline), - recognizer: TapGestureRecognizer() - ..onTap = () { - final String route = - "/rooms/${controller.widget.defaultSelected.type.route}"; - context.go(route); - }, - ), - if (controller.activeSpace != null) - const TextSpan( - text: " > ", - ), - if (controller.activeSpace != null) - TextSpan( - text: controller.activeSpace!.getLocalizedDisplayname(), - ), - const TextSpan( - text: " > ", - ), - TextSpan( - text: controller.currentView.string(context), - ), - ], - ), - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - ), - ), - body: MaxWidthBody( - withScrolling: false, - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TimeSpanMenuButton( - value: controller.currentTimeSpan, - onChange: (TimeSpan value) => - controller.toggleTimeSpan(context, value), - ), - AnalyticsViewButton( - value: controller.currentView, - onChange: controller.toggleView, - ), - AnalyticsLanguageButton( - value: controller - .pangeaController.analytics.currentAnalyticsLang, - onChange: (lang) => controller.toggleSpaceLang(lang), - languages: controller.widget.targetLanguages, - ), - ], - ), - const SizedBox( - height: 10, - ), - Expanded( - flex: 1, - child: chartView(context), - ), - Expanded( - flex: 1, - child: DefaultTabController( - length: 2, - child: Column( - children: [ - TabBar( - tabs: [ - ...controller.widget.tabs.map( - (tab) => Tab( - icon: Icon( - tab.icon, - color: Theme.of(context) - .colorScheme - .onSurfaceVariant, - ), - ), - ), - ], - ), - Expanded( - child: SingleChildScrollView( - child: SizedBox( - height: max( - controller.widget.tabs[0].items.length + 1, - controller.widget.tabs[1].items.length, - ) * - 72, - child: TabBarView( - physics: const NeverScrollableScrollPhysics(), - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ...controller.widget.tabs[0].items.map( - (item) => AnalyticsListTile( - refreshStream: controller.refreshStream, - avatar: item.avatar, - defaultSelected: - controller.widget.defaultSelected, - selected: AnalyticsSelected( - item.id, - controller.widget.tabs[0].type, - item.displayName, - ), - isSelected: - controller.isSelected(item.id), - onTap: (_) => controller.toggleSelection( - AnalyticsSelected( - item.id, - controller.widget.tabs[0].type, - item.displayName, - ), - ), - allowNavigateOnSelect: controller - .widget.tabs[0].allowNavigateOnSelect, - pangeaController: - controller.pangeaController, - controller: controller, - ), - ), - if (controller.widget.defaultSelected.type == - AnalyticsEntryType.space) - AnalyticsListTile( - refreshStream: controller.refreshStream, - defaultSelected: - controller.widget.defaultSelected, - avatar: null, - selected: AnalyticsSelected( - controller.widget.defaultSelected.id, - AnalyticsEntryType.privateChats, - L10n.of(context)!.allPrivateChats, - ), - allowNavigateOnSelect: false, - isSelected: controller.isSelected( - controller.widget.defaultSelected.id, - ), - onTap: controller.toggleSelection, - pangeaController: - controller.pangeaController, - controller: controller, - ), - ], - ), - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: controller.widget.tabs[1].items - .map( - (item) => AnalyticsListTile( - refreshStream: controller.refreshStream, - avatar: item.avatar, - defaultSelected: - controller.widget.defaultSelected, - selected: AnalyticsSelected( - item.id, - controller.widget.tabs[1].type, - item.displayName, - ), - isSelected: - controller.isSelected(item.id), - onTap: controller.toggleSelection, - allowNavigateOnSelect: controller.widget - .tabs[1].allowNavigateOnSelect, - pangeaController: - controller.pangeaController, - controller: controller, - ), - ) - .toList(), - ), - ], - ), - ), - ), - ), - ], - ), - ), - ), - ], - ), - ), - ); - } -} +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// appBar: AppBar( +// centerTitle: true, +// title: RichText( +// text: TextSpan( +// style: TextStyle( +// color: Theme.of(context).textTheme.bodyLarge!.color, +// fontSize: 18, +// fontWeight: FontWeight.w700, +// ), +// children: [ +// TextSpan( +// text: controller.widget.pageTitle, +// style: const TextStyle(decoration: TextDecoration.underline), +// recognizer: TapGestureRecognizer() +// ..onTap = () { +// final String route = +// "/rooms/${controller.widget.defaultSelected.type.route}"; +// context.go(route); +// }, +// ), +// if (controller.activeSpace != null) +// const TextSpan( +// text: " > ", +// ), +// if (controller.activeSpace != null) +// TextSpan( +// text: controller.activeSpace!.getLocalizedDisplayname(), +// ), +// const TextSpan( +// text: " > ", +// ), +// TextSpan( +// text: controller.currentView.string(context), +// ), +// ], +// ), +// overflow: TextOverflow.ellipsis, +// textAlign: TextAlign.center, +// ), +// ), +// body: MaxWidthBody( +// withScrolling: false, +// child: Column( +// children: [ +// Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// TimeSpanMenuButton( +// value: controller.currentTimeSpan, +// onChange: (TimeSpan value) => +// controller.toggleTimeSpan(context, value), +// ), +// AnalyticsViewButton( +// value: controller.currentView, +// onChange: controller.toggleView, +// ), +// AnalyticsLanguageButton( +// value: controller +// .pangeaController.analytics.currentAnalyticsLang, +// onChange: (lang) => controller.toggleSpaceLang(lang), +// languages: controller.widget.targetLanguages, +// ), +// ], +// ), +// const SizedBox( +// height: 10, +// ), +// Expanded( +// flex: 1, +// child: chartView(context), +// ), +// Expanded( +// flex: 1, +// child: DefaultTabController( +// length: 2, +// child: Column( +// children: [ +// TabBar( +// tabs: [ +// ...controller.widget.tabs.map( +// (tab) => Tab( +// icon: Icon( +// tab.icon, +// color: Theme.of(context) +// .colorScheme +// .onSurfaceVariant, +// ), +// ), +// ), +// ], +// ), +// Expanded( +// child: SingleChildScrollView( +// child: SizedBox( +// height: max( +// controller.widget.tabs[0].items.length + 1, +// controller.widget.tabs[1].items.length, +// ) * +// 72, +// child: TabBarView( +// physics: const NeverScrollableScrollPhysics(), +// children: [ +// Column( +// crossAxisAlignment: CrossAxisAlignment.stretch, +// children: [ +// ...controller.widget.tabs[0].items.map( +// (item) => AnalyticsListTile( +// refreshStream: controller.refreshStream, +// avatar: item.avatar, +// defaultSelected: +// controller.widget.defaultSelected, +// selected: AnalyticsSelected( +// item.id, +// controller.widget.tabs[0].type, +// item.displayName, +// ), +// isSelected: +// controller.isSelected(item.id), +// onTap: (_) => controller.toggleSelection( +// AnalyticsSelected( +// item.id, +// controller.widget.tabs[0].type, +// item.displayName, +// ), +// ), +// allowNavigateOnSelect: controller +// .widget.tabs[0].allowNavigateOnSelect, +// pangeaController: +// controller.pangeaController, +// controller: controller, +// ), +// ), +// if (controller.widget.defaultSelected.type == +// AnalyticsEntryType.space) +// AnalyticsListTile( +// refreshStream: controller.refreshStream, +// defaultSelected: +// controller.widget.defaultSelected, +// avatar: null, +// selected: AnalyticsSelected( +// controller.widget.defaultSelected.id, +// AnalyticsEntryType.privateChats, +// L10n.of(context)!.allPrivateChats, +// ), +// allowNavigateOnSelect: false, +// isSelected: controller.isSelected( +// controller.widget.defaultSelected.id, +// ), +// onTap: controller.toggleSelection, +// pangeaController: +// controller.pangeaController, +// controller: controller, +// ), +// ], +// ), +// Column( +// crossAxisAlignment: CrossAxisAlignment.stretch, +// children: controller.widget.tabs[1].items +// .map( +// (item) => AnalyticsListTile( +// refreshStream: controller.refreshStream, +// avatar: item.avatar, +// defaultSelected: +// controller.widget.defaultSelected, +// selected: AnalyticsSelected( +// item.id, +// controller.widget.tabs[1].type, +// item.displayName, +// ), +// isSelected: +// controller.isSelected(item.id), +// onTap: controller.toggleSelection, +// allowNavigateOnSelect: controller.widget +// .tabs[1].allowNavigateOnSelect, +// pangeaController: +// controller.pangeaController, +// controller: controller, +// ), +// ) +// .toList(), +// ), +// ], +// ), +// ), +// ), +// ), +// ], +// ), +// ), +// ), +// ], +// ), +// ), +// ); +// } +// } diff --git a/lib/pangea/pages/analytics/construct_list.dart b/lib/pangea/pages/analytics/construct_list.dart index d46936c86..0f06ddc8d 100644 --- a/lib/pangea/pages/analytics/construct_list.dart +++ b/lib/pangea/pages/analytics/construct_list.dart @@ -4,6 +4,7 @@ import 'package:collection/collection.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; +import 'package:fluffychat/pangea/enum/time_span.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/models/analytics/constructs_event.dart'; @@ -22,7 +23,7 @@ class ConstructList extends StatefulWidget { final ConstructTypeEnum constructType; final AnalyticsSelected defaultSelected; final AnalyticsSelected? selected; - final BaseAnalyticsController controller; + final TimeSpan timeSpan; final PangeaController pangeaController; final StreamController refreshStream; @@ -30,9 +31,9 @@ class ConstructList extends StatefulWidget { super.key, required this.constructType, required this.defaultSelected, - required this.controller, required this.pangeaController, required this.refreshStream, + required this.timeSpan, this.selected, }); @@ -53,11 +54,11 @@ class ConstructListState extends State { : Column( children: [ ConstructListView( - controller: widget.controller, pangeaController: widget.pangeaController, defaultSelected: widget.defaultSelected, selected: widget.selected, refreshStream: widget.refreshStream, + timeSpan: widget.timeSpan, ), ], ); @@ -74,17 +75,17 @@ class ConstructListState extends State { // subtitle = total uses, equal to construct.content.uses.length // list has a fixed height of 400 and is scrollable class ConstructListView extends StatefulWidget { - final BaseAnalyticsController controller; final PangeaController pangeaController; final AnalyticsSelected defaultSelected; + final TimeSpan timeSpan; final AnalyticsSelected? selected; final StreamController refreshStream; const ConstructListView({ super.key, - required this.controller, required this.pangeaController, required this.defaultSelected, + required this.timeSpan, required this.refreshStream, this.selected, }); @@ -101,6 +102,7 @@ class ConstructListViewState extends State { bool fetchingConstructs = true; bool fetchingUses = false; StreamSubscription? refreshSubscription; + String? currentLemma; @override void initState() { @@ -112,6 +114,7 @@ class ConstructListViewState extends State { defaultSelected: widget.defaultSelected, selected: widget.selected, forceUpdate: true, + timeSpan: widget.timeSpan, ) .whenComplete(() => setState(() => fetchingConstructs = false)) .then((value) => setState(() => _constructs = value)); @@ -127,6 +130,7 @@ class ConstructListViewState extends State { defaultSelected: widget.defaultSelected, selected: widget.selected, forceUpdate: true, + timeSpan: widget.timeSpan, ) .then( (value) => setState(() { @@ -143,9 +147,14 @@ class ConstructListViewState extends State { super.dispose(); } + void setCurrentLemma(String? lemma) { + currentLemma = lemma; + setState(() {}); + } + int get lemmaIndex => constructs?.indexWhere( - (element) => element.lemma == widget.controller.currentLemma, + (element) => element.lemma == currentLemma, ) ?? -1; @@ -258,7 +267,7 @@ class ConstructListViewState extends State { } ConstructUses? get currentConstruct => constructs?.firstWhereOrNull( - (element) => element.lemma == widget.controller.currentLemma, + (element) => element.lemma == currentLemma, ); // given the current lemma and list of message events, return a list of @@ -266,7 +275,7 @@ class ConstructListViewState extends State { // this is because some message events may have has more than one PangeaMatch of a // given lemma type. List getMessageEventMatches() { - if (widget.controller.currentLemma == null) return []; + if (currentLemma == null) return []; final List allMsgErrorSteps = []; for (final msgEvent in _msgEvents) { @@ -277,7 +286,7 @@ class ConstructListViewState extends State { } // get all the pangea matches in that message which have that lemma final List? msgErrorSteps = msgEvent.errorSteps( - widget.controller.currentLemma!, + currentLemma!, ); if (msgErrorSteps == null) continue; @@ -327,7 +336,7 @@ class ConstructListViewState extends State { ), onTap: () async { final String lemma = constructs![index].lemma; - widget.controller.setCurrentLemma(lemma); + setCurrentLemma(lemma); fetchUses().then((_) => showConstructMessagesDialog()); }, ); @@ -346,7 +355,7 @@ class ConstructMessagesDialog extends StatelessWidget { @override Widget build(BuildContext context) { - if (controller.widget.controller.currentLemma == null || + if (controller.currentLemma == null || controller.constructs == null || controller.lemmaIndex < 0 || controller.lemmaIndex >= controller.constructs!.length) { @@ -359,7 +368,7 @@ class ConstructMessagesDialog extends StatelessWidget { controller._msgEvents.length; return AlertDialog( - title: Center(child: Text(controller.widget.controller.currentLemma!)), + title: Center(child: Text(controller.currentLemma!)), content: SizedBox( height: noData ? 90 : 250, width: noData ? 200 : 400, @@ -380,7 +389,7 @@ class ConstructMessagesDialog extends StatelessWidget { children: [ ConstructMessage( msgEvent: event.msgEvent, - lemma: controller.widget.controller.currentLemma!, + lemma: controller.currentLemma!, errorMessage: event.lemmaMatch, ), if (index < msgEventMatches.length - 1) @@ -528,42 +537,37 @@ class ConstructMessageBubble extends StatelessWidget { vertical: 8, ), child: RichText( - text: (end == null) - ? TextSpan( - text: errorText, - style: defaultStyle, - ) - : TextSpan( - children: [ - TextSpan( - text: errorText.substring(0, start), - style: defaultStyle, - ), - TextSpan( - text: errorText.substring(start, end), - style: defaultStyle.merge( - TextStyle( - backgroundColor: Colors.red.withOpacity(0.25), - decoration: TextDecoration.lineThrough, - decorationThickness: 2.5, - ), - ), - ), - const TextSpan(text: " "), - TextSpan( - text: replacementText, - style: defaultStyle.merge( - TextStyle( - backgroundColor: Colors.green.withOpacity(0.25), - ), - ), - ), - TextSpan( - text: errorText.substring(end), - style: defaultStyle, - ), - ], + text: TextSpan( + children: [ + TextSpan( + text: errorText.substring(0, start), + style: defaultStyle, + ), + TextSpan( + text: errorText.substring(start, end), + style: defaultStyle.merge( + TextStyle( + backgroundColor: Colors.red.withOpacity(0.25), + decoration: TextDecoration.lineThrough, + decorationThickness: 2.5, + ), ), + ), + const TextSpan(text: " "), + TextSpan( + text: replacementText, + style: defaultStyle.merge( + TextStyle( + backgroundColor: Colors.green.withOpacity(0.25), + ), + ), + ), + TextSpan( + text: errorText.substring(end), + style: defaultStyle, + ), + ], + ), ), ), ), diff --git a/lib/pangea/pages/analytics/list_summary_analytics.dart b/lib/pangea/pages/analytics/list_summary_analytics.dart index bf388cea7..02bb5d3fb 100644 --- a/lib/pangea/pages/analytics/list_summary_analytics.dart +++ b/lib/pangea/pages/analytics/list_summary_analytics.dart @@ -1,101 +1,101 @@ -import 'dart:math'; +// import 'dart:math'; -import 'package:fluffychat/pangea/models/analytics/chart_analytics_model.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; +// import 'package:fluffychat/pangea/models/analytics/chart_analytics_model.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_gen/gen_l10n/l10n.dart'; -import '../../enum/use_type.dart'; +// import '../../enum/use_type.dart'; -class ListSummaryAnalytics extends StatelessWidget { - final ChartAnalyticsModel? chartAnalytics; +// class ListSummaryAnalytics extends StatelessWidget { +// final ChartAnalyticsModel? chartAnalytics; - const ListSummaryAnalytics({super.key, this.chartAnalytics}); +// const ListSummaryAnalytics({super.key, this.chartAnalytics}); - TimeSeriesTotals? get totals => chartAnalytics?.totals; +// TimeSeriesTotals? get totals => chartAnalytics?.totals; - String spacer(int baseLength, int number) => - " " * max(baseLength - number.toString().length, 0); +// String spacer(int baseLength, int number) => +// " " * max(baseLength - number.toString().length, 0); - WidgetSpan spacerIconText( - String toolTip, - String space, - IconData icon, - int value, - Color? color, [ - percentage = true, - ]) => - WidgetSpan( - child: Tooltip( - message: toolTip, - child: RichText( - text: TextSpan( - children: [ - TextSpan( - text: space, - ), - WidgetSpan(child: Icon(icon, size: 14, color: color)), - TextSpan( - text: " $value${percentage ? "%" : ""}", - style: TextStyle(color: color), - ), - ], - ), - ), - ), - ); +// WidgetSpan spacerIconText( +// String toolTip, +// String space, +// IconData icon, +// int value, +// Color? color, [ +// percentage = true, +// ]) => +// WidgetSpan( +// child: Tooltip( +// message: toolTip, +// child: RichText( +// text: TextSpan( +// children: [ +// TextSpan( +// text: space, +// ), +// WidgetSpan(child: Icon(icon, size: 14, color: color)), +// TextSpan( +// text: " $value${percentage ? "%" : ""}", +// style: TextStyle(color: color), +// ), +// ], +// ), +// ), +// ), +// ); - @override - Widget build(BuildContext context) { - if (totals == null) { - return const LinearProgressIndicator(); - } - final l10n = L10n.of(context); +// @override +// Widget build(BuildContext context) { +// if (totals == null) { +// return const LinearProgressIndicator(); +// } +// final l10n = L10n.of(context); - return RichText( - text: TextSpan( - children: [ - spacerIconText( - L10n.of(context) != null - ? L10n.of(context)!.totalMessages - : "Total messages sent", - "", - Icons.chat_bubble, - totals!.all, - Theme.of(context).textTheme.bodyLarge!.color, - false, - ), - if (totals!.all != 0) ...[ - spacerIconText( - l10n != null ? l10n.taTooltip : "With translation assistance", - spacer(8, totals!.all), - UseType.ta.iconData, - totals!.taPercent, - UseType.ta.color(context), - ), - spacerIconText( - l10n != null ? l10n.gaTooltip : "With grammar assistance", - spacer(4, totals!.taPercent), - UseType.ga.iconData, - totals!.gaPercent, - UseType.ga.color(context), - ), - spacerIconText( - l10n != null ? l10n.waTooltip : "Without assistance", - spacer(4, totals!.gaPercent), - UseType.wa.iconData, - totals!.waPercent, - UseType.wa.color(context), - ), - spacerIconText( - l10n != null ? l10n.unTooltip : "Other", - spacer(4, totals!.waPercent), - UseType.un.iconData, - totals!.unPercent, - UseType.un.color(context), - ), - ], - ], - ), - ); - } -} +// return RichText( +// text: TextSpan( +// children: [ +// spacerIconText( +// L10n.of(context) != null +// ? L10n.of(context)!.totalMessages +// : "Total messages sent", +// "", +// Icons.chat_bubble, +// totals!.all, +// Theme.of(context).textTheme.bodyLarge!.color, +// false, +// ), +// if (totals!.all != 0) ...[ +// spacerIconText( +// l10n != null ? l10n.taTooltip : "With translation assistance", +// spacer(8, totals!.all), +// UseType.ta.iconData, +// totals!.taPercent, +// UseType.ta.color(context), +// ), +// spacerIconText( +// l10n != null ? l10n.gaTooltip : "With grammar assistance", +// spacer(4, totals!.taPercent), +// UseType.ga.iconData, +// totals!.gaPercent, +// UseType.ga.color(context), +// ), +// spacerIconText( +// l10n != null ? l10n.waTooltip : "Without assistance", +// spacer(4, totals!.gaPercent), +// UseType.wa.iconData, +// totals!.waPercent, +// UseType.wa.color(context), +// ), +// spacerIconText( +// l10n != null ? l10n.unTooltip : "Other", +// spacer(4, totals!.waPercent), +// UseType.un.iconData, +// totals!.unPercent, +// UseType.un.color(context), +// ), +// ], +// ], +// ), +// ); +// } +// } diff --git a/lib/pangea/pages/analytics/messages_bar_chart.dart b/lib/pangea/pages/analytics/messages_bar_chart.dart index 509270edb..5a9002243 100644 --- a/lib/pangea/pages/analytics/messages_bar_chart.dart +++ b/lib/pangea/pages/analytics/messages_bar_chart.dart @@ -1,402 +1,402 @@ -import 'dart:developer'; +// import 'dart:developer'; -import 'package:fl_chart/fl_chart.dart'; -import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/pangea/pages/analytics/bar_chart_placeholder_data.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; +// import 'package:fl_chart/fl_chart.dart'; +// import 'package:fluffychat/config/themes.dart'; +// import 'package:fluffychat/pangea/pages/analytics/bar_chart_placeholder_data.dart'; +// import 'package:fluffychat/pangea/utils/error_handler.dart'; +// import 'package:flutter/foundation.dart'; +// import 'package:flutter/material.dart'; +// import 'package:intl/intl.dart'; -import '../../enum/time_span.dart'; -import '../../enum/use_type.dart'; -import '../../models/analytics/chart_analytics_model.dart'; -import 'bar_chart_card.dart'; -import 'messages_legend_widget.dart'; +// import '../../enum/time_span.dart'; +// import '../../enum/use_type.dart'; +// import '../../models/analytics/chart_analytics_model.dart'; +// import 'bar_chart_card.dart'; +// import 'messages_legend_widget.dart'; -class MessagesBarChart extends StatefulWidget { - final ChartAnalyticsModel? chartAnalytics; +// class MessagesBarChart extends StatefulWidget { +// final ChartAnalyticsModel? chartAnalytics; - const MessagesBarChart({ - super.key, - required this.chartAnalytics, - }); +// const MessagesBarChart({ +// super.key, +// required this.chartAnalytics, +// }); - @override - State createState() => MessagesBarChartState(); -} +// @override +// State createState() => MessagesBarChartState(); +// } -class MessagesBarChartState extends State { - final double barSpace = 16; - final List> intervalGroupings = []; +// class MessagesBarChartState extends State { +// final double barSpace = 16; +// final List> intervalGroupings = []; - @override - initState() { - super.initState(); - } +// @override +// initState() { +// super.initState(); +// } - @override - Widget build(BuildContext context) { - final flLine = FlLine( - color: Theme.of(context).dividerColor, - strokeWidth: 1, - ); +// @override +// Widget build(BuildContext context) { +// final flLine = FlLine( +// color: Theme.of(context).dividerColor, +// strokeWidth: 1, +// ); - final flTitlesData = FlTitlesData( - show: true, - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - reservedSize: 28, - getTitlesWidget: bottomTitles, - ), - ), - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - reservedSize: 40, - getTitlesWidget: leftTitles, - ), - ), - topTitles: const AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - rightTitles: const AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - ); - final barChartData = BarChartData( - alignment: BarChartAlignment.spaceEvenly, - barTouchData: BarTouchData( - enabled: false, - ), - // barTouchData: barTouchData, - titlesData: flTitlesData, - gridData: FlGridData( - show: true, - // checkToShowHorizontalLine: (value) => value % 10 == 0, - checkToShowHorizontalLine: (value) => true, - getDrawingHorizontalLine: (value) => flLine, - checkToShowVerticalLine: (value) => false, - getDrawingVerticalLine: (value) => flLine, - ), - borderData: FlBorderData( - show: false, - ), - groupsSpace: barSpace, - barGroups: barChartGroupData, - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - ); - final barChart = BarChart( - barChartData, - swapAnimationDuration: const Duration(milliseconds: 250), - ); +// final flTitlesData = FlTitlesData( +// show: true, +// bottomTitles: AxisTitles( +// sideTitles: SideTitles( +// showTitles: true, +// reservedSize: 28, +// getTitlesWidget: bottomTitles, +// ), +// ), +// leftTitles: AxisTitles( +// sideTitles: SideTitles( +// showTitles: true, +// reservedSize: 40, +// getTitlesWidget: leftTitles, +// ), +// ), +// topTitles: const AxisTitles( +// sideTitles: SideTitles(showTitles: false), +// ), +// rightTitles: const AxisTitles( +// sideTitles: SideTitles(showTitles: false), +// ), +// ); +// final barChartData = BarChartData( +// alignment: BarChartAlignment.spaceEvenly, +// barTouchData: BarTouchData( +// enabled: false, +// ), +// // barTouchData: barTouchData, +// titlesData: flTitlesData, +// gridData: FlGridData( +// show: true, +// // checkToShowHorizontalLine: (value) => value % 10 == 0, +// checkToShowHorizontalLine: (value) => true, +// getDrawingHorizontalLine: (value) => flLine, +// checkToShowVerticalLine: (value) => false, +// getDrawingVerticalLine: (value) => flLine, +// ), +// borderData: FlBorderData( +// show: false, +// ), +// groupsSpace: barSpace, +// barGroups: barChartGroupData, +// backgroundColor: Theme.of(context).scaffoldBackgroundColor, +// ); +// final barChart = BarChart( +// barChartData, +// swapAnimationDuration: const Duration(milliseconds: 250), +// ); - return BarChartCard( - barChart: barChart, - loadingData: widget.chartAnalytics == null, - legend: const MessagesLegendsListWidget(), - ); - } +// return BarChartCard( +// barChart: barChart, +// loadingData: widget.chartAnalytics == null, +// legend: const MessagesLegendsListWidget(), +// ); +// } - bool showLabelBasedOnTimeSpan( - TimeSpan timeSpan, - TimeSeriesInterval current, - TimeSeriesInterval? last, - int labelIndex, - ) { - switch (timeSpan) { - case TimeSpan.day: - return current.end.hour % 3 == 0; - case TimeSpan.month: - if (current.end.month != last?.end.month) { - return true; - } - double width = MediaQuery.of(context).size.width; - if (FluffyThemes.isColumnMode(context)) { - width = width - FluffyThemes.navRailWidth - FluffyThemes.columnWidth; - } - const int numDays = 28; - const int minSpacePerDay = 20; - final int availableSpaces = width ~/ minSpacePerDay; - final int showAtInterval = (numDays / availableSpaces).floor() + 1; +// bool showLabelBasedOnTimeSpan( +// TimeSpan timeSpan, +// TimeSeriesInterval current, +// TimeSeriesInterval? last, +// int labelIndex, +// ) { +// switch (timeSpan) { +// case TimeSpan.day: +// return current.end.hour % 3 == 0; +// case TimeSpan.month: +// if (current.end.month != last?.end.month) { +// return true; +// } +// double width = MediaQuery.of(context).size.width; +// if (FluffyThemes.isColumnMode(context)) { +// width = width - FluffyThemes.navRailWidth - FluffyThemes.columnWidth; +// } +// const int numDays = 28; +// const int minSpacePerDay = 20; +// final int availableSpaces = width ~/ minSpacePerDay; +// final int showAtInterval = (numDays / availableSpaces).floor() + 1; - final int lastDayOfCurrentMonth = - DateTime(current.end.year, current.end.month + 1, 0).day; - final bool isNextToMonth = labelIndex == 1 || - current.end.day == 2 || - current.end.day == lastDayOfCurrentMonth; - final bool shouldShowNextToMonth = showAtInterval <= 1; - return (current.end.day % showAtInterval == 0) && - (!isNextToMonth || shouldShowNextToMonth); - case TimeSpan.week: - case TimeSpan.sixmonths: - case TimeSpan.year: - default: - return true; - } - } +// final int lastDayOfCurrentMonth = +// DateTime(current.end.year, current.end.month + 1, 0).day; +// final bool isNextToMonth = labelIndex == 1 || +// current.end.day == 2 || +// current.end.day == lastDayOfCurrentMonth; +// final bool shouldShowNextToMonth = showAtInterval <= 1; +// return (current.end.day % showAtInterval == 0) && +// (!isNextToMonth || shouldShowNextToMonth); +// case TimeSpan.week: +// case TimeSpan.sixmonths: +// case TimeSpan.year: +// default: +// return true; +// } +// } - String getLabelBasedOnTimeSpan( - TimeSpan timeSpan, - TimeSeriesInterval current, - TimeSeriesInterval? last, - int labelIndex, - ) { - final bool showLabel = showLabelBasedOnTimeSpan( - timeSpan, - current, - last, - labelIndex, - ); +// String getLabelBasedOnTimeSpan( +// TimeSpan timeSpan, +// TimeSeriesInterval current, +// TimeSeriesInterval? last, +// int labelIndex, +// ) { +// final bool showLabel = showLabelBasedOnTimeSpan( +// timeSpan, +// current, +// last, +// labelIndex, +// ); - if (widget.chartAnalytics == null || !showLabel) { - return ""; - } - if (isInSameGroup(last, current, timeSpan)) { - return "-"; - } +// if (widget.chartAnalytics == null || !showLabel) { +// return ""; +// } +// if (isInSameGroup(last, current, timeSpan)) { +// return "-"; +// } - switch (widget.chartAnalytics?.timeSpan ?? TimeSpan.month) { - case TimeSpan.day: - return DateFormat(DateFormat.HOUR).format(current.end); - case TimeSpan.week: - return DateFormat(DateFormat.ABBR_WEEKDAY).format(current.end); - case TimeSpan.month: - return current.end.month != last?.end.month - ? DateFormat(DateFormat.ABBR_MONTH).format(current.end) - : DateFormat(DateFormat.DAY).format(current.end); - case TimeSpan.sixmonths: - case TimeSpan.year: - return DateFormat(DateFormat.ABBR_STANDALONE_MONTH).format(current.end); - default: - return ''; - } - } +// switch (widget.chartAnalytics?.timeSpan ?? TimeSpan.month) { +// case TimeSpan.day: +// return DateFormat(DateFormat.HOUR).format(current.end); +// case TimeSpan.week: +// return DateFormat(DateFormat.ABBR_WEEKDAY).format(current.end); +// case TimeSpan.month: +// return current.end.month != last?.end.month +// ? DateFormat(DateFormat.ABBR_MONTH).format(current.end) +// : DateFormat(DateFormat.DAY).format(current.end); +// case TimeSpan.sixmonths: +// case TimeSpan.year: +// return DateFormat(DateFormat.ABBR_STANDALONE_MONTH).format(current.end); +// default: +// return ''; +// } +// } - Widget bottomTitles(double value, TitleMeta meta) { - if (widget.chartAnalytics == null) { - return Container(); - } - String text; - final index = value.toInt(); - final TimeSpan timeSpan = widget.chartAnalytics?.timeSpan ?? TimeSpan.month; - final TimeSeriesInterval? last = - index != 0 ? intervalGroupings[index - 1].last : null; - final TimeSeriesInterval current = intervalGroupings[index].last; +// Widget bottomTitles(double value, TitleMeta meta) { +// if (widget.chartAnalytics == null) { +// return Container(); +// } +// String text; +// final index = value.toInt(); +// final TimeSpan timeSpan = widget.chartAnalytics?.timeSpan ?? TimeSpan.month; +// final TimeSeriesInterval? last = +// index != 0 ? intervalGroupings[index - 1].last : null; +// final TimeSeriesInterval current = intervalGroupings[index].last; - text = getLabelBasedOnTimeSpan(timeSpan, current, last, index); +// text = getLabelBasedOnTimeSpan(timeSpan, current, last, index); - return SideTitleWidget( - axisSide: meta.axisSide, - child: Text( - text, - style: titleTextStyle(context), - ), - ); - } +// return SideTitleWidget( +// axisSide: meta.axisSide, +// child: Text( +// text, +// style: titleTextStyle(context), +// ), +// ); +// } - TextStyle titleTextStyle(context) => TextStyle( - color: Theme.of(context).textTheme.bodyLarge!.color, - fontSize: 10, - ); +// TextStyle titleTextStyle(context) => TextStyle( +// color: Theme.of(context).textTheme.bodyLarge!.color, +// fontSize: 10, +// ); - Widget leftTitles(double value, TitleMeta meta) { - Widget textWidget; - if (value != meta.max) { - textWidget = Text(meta.formattedValue, style: titleTextStyle(context)); - } else { - textWidget = const Icon(Icons.chat_bubble, size: 14); - } - return SideTitleWidget( - axisSide: meta.axisSide, - child: textWidget, - ); - } +// Widget leftTitles(double value, TitleMeta meta) { +// Widget textWidget; +// if (value != meta.max) { +// textWidget = Text(meta.formattedValue, style: titleTextStyle(context)); +// } else { +// textWidget = const Icon(Icons.chat_bubble, size: 14); +// } +// return SideTitleWidget( +// axisSide: meta.axisSide, +// child: textWidget, +// ); +// } - bool isInSameGroup( - TimeSeriesInterval? t1, - TimeSeriesInterval t2, - TimeSpan timeSpan, - ) { - final DateTime? date1 = t1?.end; - final DateTime date2 = t2.end; - if (timeSpan == TimeSpan.sixmonths || timeSpan == TimeSpan.year) { - return date1?.month == date2.month; - } else if (timeSpan == TimeSpan.week) { - return date1?.day == date2.day; - } else { - return false; - } - } +// bool isInSameGroup( +// TimeSeriesInterval? t1, +// TimeSeriesInterval t2, +// TimeSpan timeSpan, +// ) { +// final DateTime? date1 = t1?.end; +// final DateTime date2 = t2.end; +// if (timeSpan == TimeSpan.sixmonths || timeSpan == TimeSpan.year) { +// return date1?.month == date2.month; +// } else if (timeSpan == TimeSpan.week) { +// return date1?.day == date2.day; +// } else { +// return false; +// } +// } - void makeIntervalGroupings() { - intervalGroupings.clear(); - try { - for (final timeSeriesInterval - in widget.chartAnalytics?.timeSeries ?? []) { - //Note: if we decide we'd like to do some sort of grouping in the future, - // this is where that could happen. Currently, we're just putting one - // BarChartRod in each BarChartGroup - final TimeSeriesInterval? last = - intervalGroupings.isNotEmpty ? intervalGroupings.last.last : null; +// void makeIntervalGroupings() { +// intervalGroupings.clear(); +// try { +// for (final timeSeriesInterval +// in widget.chartAnalytics?.timeSeries ?? []) { +// //Note: if we decide we'd like to do some sort of grouping in the future, +// // this is where that could happen. Currently, we're just putting one +// // BarChartRod in each BarChartGroup +// final TimeSeriesInterval? last = +// intervalGroupings.isNotEmpty ? intervalGroupings.last.last : null; - if (widget.chartAnalytics != null && - isInSameGroup( - last, - timeSeriesInterval, - widget.chartAnalytics!.timeSpan, - )) { - intervalGroupings.last.add(timeSeriesInterval); - } else { - intervalGroupings.add([timeSeriesInterval]); - } - } - } catch (err, stack) { - debugger(when: kDebugMode); - ErrorHandler.logError(e: err, s: stack); - } - } +// if (widget.chartAnalytics != null && +// isInSameGroup( +// last, +// timeSeriesInterval, +// widget.chartAnalytics!.timeSpan, +// )) { +// intervalGroupings.last.add(timeSeriesInterval); +// } else { +// intervalGroupings.add([timeSeriesInterval]); +// } +// } +// } catch (err, stack) { +// debugger(when: kDebugMode); +// ErrorHandler.logError(e: err, s: stack); +// } +// } - List get barChartGroupData { - if (widget.chartAnalytics == null) { - return BarChartPlaceHolderData.getRandomData(context); - } +// List get barChartGroupData { +// if (widget.chartAnalytics == null) { +// return BarChartPlaceHolderData.getRandomData(context); +// } - makeIntervalGroupings(); +// makeIntervalGroupings(); - final List chartData = []; +// final List chartData = []; - intervalGroupings.asMap().forEach((index, intervalGroup) { - chartData.add( - BarChartGroupData( - x: index, - barsSpace: barSpace, - // barRods: intervalGroup.map(constructBarChartRodData).toList(), - barRods: constructBarChartRodData(intervalGroup), - ), - ); - }); - return chartData; - } +// intervalGroupings.asMap().forEach((index, intervalGroup) { +// chartData.add( +// BarChartGroupData( +// x: index, +// barsSpace: barSpace, +// // barRods: intervalGroup.map(constructBarChartRodData).toList(), +// barRods: constructBarChartRodData(intervalGroup), +// ), +// ); +// }); +// return chartData; +// } - // BarChartRodData constructBarChartRodData(TimeSeriesInterval timeSeriesInterval) { - // final double y1 = timeSeriesInterval.spanIT.toDouble(); - // final double y2 = - // (timeSeriesInterval.spanIT + timeSeriesInterval.spanIGC).toDouble(); - // final double y3 = timeSeriesInterval.spanTotal.toDouble(); - // return BarChartRodData( - // toY: y3, - // width: 10.toDouble(), - // rodStackItems: [ - // BarChartRodStackItem(0, y1, UseType.ta.color(context)), - // BarChartRodStackItem(y1, y2, UseType.ga.color(context)), - // BarChartRodStackItem(y2, y3, UseType.wa.color(context)), - // ], - // borderRadius: BorderRadius.zero, - // ); - // } +// // BarChartRodData constructBarChartRodData(TimeSeriesInterval timeSeriesInterval) { +// // final double y1 = timeSeriesInterval.spanIT.toDouble(); +// // final double y2 = +// // (timeSeriesInterval.spanIT + timeSeriesInterval.spanIGC).toDouble(); +// // final double y3 = timeSeriesInterval.spanTotal.toDouble(); +// // return BarChartRodData( +// // toY: y3, +// // width: 10.toDouble(), +// // rodStackItems: [ +// // BarChartRodStackItem(0, y1, UseType.ta.color(context)), +// // BarChartRodStackItem(y1, y2, UseType.ga.color(context)), +// // BarChartRodStackItem(y2, y3, UseType.wa.color(context)), +// // ], +// // borderRadius: BorderRadius.zero, +// // ); +// // } - List constructBarChartRodData( - List timeSeriesIntervalGroup, - ) { - int y1 = 0; - int y2 = 0; - int y3 = 0; - int y4 = 0; - for (final e in timeSeriesIntervalGroup) { - y1 += e.totals.ta; - y2 += y1 + e.totals.ga; - y3 += y2 + e.totals.wa; - y4 += y3 + e.totals.un; - } - return [ - BarChartRodData( - toY: y4.toDouble(), - width: 10.toDouble(), - rodStackItems: [ - BarChartRodStackItem(0, y1.toDouble(), UseType.ta.color(context)), - BarChartRodStackItem( - y1.toDouble(), - y2.toDouble(), - UseType.ga.color(context), - ), - BarChartRodStackItem( - y2.toDouble(), - y3.toDouble(), - UseType.wa.color(context), - ), - BarChartRodStackItem( - y3.toDouble(), - y4.toDouble(), - UseType.un.color(context), - ), - ], - borderRadius: BorderRadius.zero, - ), - ]; - } +// List constructBarChartRodData( +// List timeSeriesIntervalGroup, +// ) { +// int y1 = 0; +// int y2 = 0; +// int y3 = 0; +// int y4 = 0; +// for (final e in timeSeriesIntervalGroup) { +// y1 += e.totals.ta; +// y2 += y1 + e.totals.ga; +// y3 += y2 + e.totals.wa; +// y4 += y3 + e.totals.un; +// } +// return [ +// BarChartRodData( +// toY: y4.toDouble(), +// width: 10.toDouble(), +// rodStackItems: [ +// BarChartRodStackItem(0, y1.toDouble(), UseType.ta.color(context)), +// BarChartRodStackItem( +// y1.toDouble(), +// y2.toDouble(), +// UseType.ga.color(context), +// ), +// BarChartRodStackItem( +// y2.toDouble(), +// y3.toDouble(), +// UseType.wa.color(context), +// ), +// BarChartRodStackItem( +// y3.toDouble(), +// y4.toDouble(), +// UseType.un.color(context), +// ), +// ], +// borderRadius: BorderRadius.zero, +// ), +// ]; +// } - // BarTouchData get barTouchData => BarTouchData( - // touchTooltipData: BarTouchTooltipData( - // fitInsideVertically: true, - // tooltipBgColor: Colors.blueGrey, - // getTooltipItem: (group, groupIndex, rod, rodIndex) { - // return BarTooltipItem( - // "groupindex $groupIndex rodIndex $rodIndex", - // const TextStyle( - // color: Colors.white, - // fontWeight: FontWeight.bold, - // fontSize: 18, - // ), - // children: [ - // toolTipText(rod), - // ], - // ); - // }, - // ), - // // touchCallback: (FlTouchEvent event, barTouchResponse) { - // // setState(() { - // // if (!event.isInterestedForInteractions || - // // barTouchResponse == null || - // // barTouchResponse.spot == null) { - // // touchedIndex = -1; - // // return; - // // } - // // touchedIndex = barTouchResponse.spot!.touchedBarGroupIndex; - // // }); - // // }, - // ); +// // BarTouchData get barTouchData => BarTouchData( +// // touchTooltipData: BarTouchTooltipData( +// // fitInsideVertically: true, +// // tooltipBgColor: Colors.blueGrey, +// // getTooltipItem: (group, groupIndex, rod, rodIndex) { +// // return BarTooltipItem( +// // "groupindex $groupIndex rodIndex $rodIndex", +// // const TextStyle( +// // color: Colors.white, +// // fontWeight: FontWeight.bold, +// // fontSize: 18, +// // ), +// // children: [ +// // toolTipText(rod), +// // ], +// // ); +// // }, +// // ), +// // // touchCallback: (FlTouchEvent event, barTouchResponse) { +// // // setState(() { +// // // if (!event.isInterestedForInteractions || +// // // barTouchResponse == null || +// // // barTouchResponse.spot == null) { +// // // touchedIndex = -1; +// // // return; +// // // } +// // // touchedIndex = barTouchResponse.spot!.touchedBarGroupIndex; +// // // }); +// // // }, +// // ); - // TextSpan toolTipText(BarChartRodData rodData) { - // double rodPercentage(int index) { - // return (rodData.rodStackItems[index].toY - - // rodData.rodStackItems[index].fromY) / - // rodData.toY * - // 100; - // } +// // TextSpan toolTipText(BarChartRodData rodData) { +// // double rodPercentage(int index) { +// // return (rodData.rodStackItems[index].toY - +// // rodData.rodStackItems[index].fromY) / +// // rodData.toY * +// // 100; +// // } - // return TextSpan( - // children: [ - // const WidgetSpan( - // child: Icon(Icons.chat_bubble, size: 14), - // ), - // TextSpan( - // text: " ${rodData.toY}", - // ), - // TextSpan( - // text: "/nIT ${rodPercentage(0)}%", - // style: TextStyle(color: UseType.ta.color(context)), - // ), - // TextSpan( - // text: " IGC ${rodPercentage(1)}%", - // style: TextStyle(color: UseType.ga.color(context)), - // ), - // TextSpan( - // text: " Direct ${rodPercentage(2)}%", - // style: TextStyle(color: UseType.wa.color(context)), - // ), - // ], - // ); - // } -} +// // return TextSpan( +// // children: [ +// // const WidgetSpan( +// // child: Icon(Icons.chat_bubble, size: 14), +// // ), +// // TextSpan( +// // text: " ${rodData.toY}", +// // ), +// // TextSpan( +// // text: "/nIT ${rodPercentage(0)}%", +// // style: TextStyle(color: UseType.ta.color(context)), +// // ), +// // TextSpan( +// // text: " IGC ${rodPercentage(1)}%", +// // style: TextStyle(color: UseType.ga.color(context)), +// // ), +// // TextSpan( +// // text: " Direct ${rodPercentage(2)}%", +// // style: TextStyle(color: UseType.wa.color(context)), +// // ), +// // ], +// // ); +// // } +// } diff --git a/lib/pangea/pages/analytics/space_analytics/space_analytics.dart b/lib/pangea/pages/analytics/space_analytics/space_analytics.dart index 50a8cb7c2..3f752bd4a 100644 --- a/lib/pangea/pages/analytics/space_analytics/space_analytics.dart +++ b/lib/pangea/pages/analytics/space_analytics/space_analytics.dart @@ -1,121 +1,114 @@ -import 'dart:async'; -import 'dart:developer'; +// import 'dart:async'; +// import 'dart:developer'; -import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; -import 'package:fluffychat/pangea/enum/bar_chart_view_enum.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/models/language_model.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/pangea/widgets/common/list_placeholder.dart'; -import 'package:fluffychat/pangea/widgets/common/p_circular_loader.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:matrix/matrix.dart'; +// import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; +// import 'package:fluffychat/pangea/enum/bar_chart_view_enum.dart'; +// import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; +// import 'package:fluffychat/pangea/models/language_model.dart'; +// import 'package:fluffychat/pangea/utils/error_handler.dart'; +// import 'package:fluffychat/pangea/widgets/common/list_placeholder.dart'; +// import 'package:fluffychat/pangea/widgets/common/p_circular_loader.dart'; +// import 'package:flutter/foundation.dart'; +// import 'package:flutter/material.dart'; +// import 'package:go_router/go_router.dart'; +// import 'package:matrix/matrix.dart'; -import '../../../../widgets/matrix.dart'; -import '../../../utils/sync_status_util_v2.dart'; -import 'space_analytics_view.dart'; +// import '../../../../widgets/matrix.dart'; +// import '../../../utils/sync_status_util_v2.dart'; -class SpaceAnalyticsPage extends StatefulWidget { - final BarChartViewSelection selectedView; - const SpaceAnalyticsPage({super.key, required this.selectedView}); +// class SpaceAnalyticsPage extends StatefulWidget { +// final BarChartViewSelection selectedView; +// const SpaceAnalyticsPage({super.key, required this.selectedView}); - @override - State createState() => SpaceAnalyticsV2Controller(); -} +// @override +// State createState() => SpaceAnalyticsV2Controller(); +// } -class SpaceAnalyticsV2Controller extends State { - bool _initialized = false; - // StreamSubscription? stateSub; - // Timer? refreshTimer; +// class SpaceAnalyticsV2Controller extends State { +// bool _initialized = false; +// // StreamSubscription? stateSub; +// // Timer? refreshTimer; - List chats = []; - List students = []; - String? get spaceId => GoRouterState.of(context).pathParameters['spaceid']; - Room? _spaceRoom; - List targetLanguages = []; +// List chats = []; +// List students = []; +// String? get spaceId => GoRouterState.of(context).pathParameters['spaceid']; +// Room? _spaceRoom; +// List targetLanguages = []; - @override - void initState() { - super.initState(); - Future.delayed(Duration.zero, () async { - if (spaceRoom == null || (!(spaceRoom?.isSpace ?? false))) { - context.go('/rooms'); - } - getChatAndStudents(); - }); - } +// @override +// void initState() { +// super.initState(); +// Future.delayed(Duration.zero, () async { +// if (spaceRoom == null || (!(spaceRoom?.isSpace ?? false))) { +// context.go('/rooms'); +// } +// getChatAndStudents(); +// }); +// } - Room? get spaceRoom { - if (_spaceRoom == null || _spaceRoom!.id != spaceId) { - debugPrint("updating _spaceRoom"); - _spaceRoom = spaceId != null - ? Matrix.of(context).client.getRoomById(spaceId!) - : null; - if (_spaceRoom == null) { - context.go('/rooms/analytics'); - return null; - } - getChatAndStudents().then((_) => setTargetLanguages()); - } - return _spaceRoom; - } +// Room? get spaceRoom { +// if (_spaceRoom == null || _spaceRoom!.id != spaceId) { +// debugPrint("updating _spaceRoom"); +// _spaceRoom = spaceId != null +// ? Matrix.of(context).client.getRoomById(spaceId!) +// : null; +// if (_spaceRoom == null) { +// context.go('/rooms/analytics'); +// return null; +// } +// getChatAndStudents().then((_) => setTargetLanguages()); +// } +// return _spaceRoom; +// } - Future getChatAndStudents() async { - try { - await spaceRoom?.requestParticipants(); +// Future getChatAndStudents() async { +// try { +// await spaceRoom?.requestParticipants(); - if (spaceRoom != null) { - final response = await Matrix.of(context).client.getSpaceHierarchy( - spaceRoom!.id, - ); +// if (spaceRoom != null) { +// final response = await Matrix.of(context).client.getSpaceHierarchy( +// spaceRoom!.id, +// ); - // set the latest fetched full hierarchy in message analytics controller - // we want to avoid calling this endpoint again and again, so whenever the - // data is made available, set it in the controller - MatrixState.pangeaController.analytics - .setLatestHierarchy(_spaceRoom!.id, response); +// students = spaceRoom!.students; +// chats = response.rooms +// .where( +// (room) => +// room.roomId != spaceRoom!.id && +// room.roomType != PangeaRoomTypes.analytics, +// ) +// .toList(); +// chats.sort((a, b) => a.roomType == 'm.space' ? -1 : 1); +// } - students = spaceRoom!.students; - chats = response.rooms - .where( - (room) => - room.roomId != spaceRoom!.id && - room.roomType != PangeaRoomTypes.analytics, - ) - .toList(); - chats.sort((a, b) => a.roomType == 'm.space' ? -1 : 1); - } +// setState(() { +// _initialized = true; +// }); +// } catch (err, s) { +// debugger(when: kDebugMode); +// ErrorHandler.logError(e: err, s: s); +// } +// } - setState(() { - _initialized = true; - }); - } catch (err, s) { - debugger(when: kDebugMode); - ErrorHandler.logError(e: err, s: s); - } - } +// Future setTargetLanguages() async { +// // get a list of language models, sorted by the +// // number of students who are learning that language +// targetLanguages = await spaceRoom?.targetLanguages() ?? []; +// setState(() {}); +// } - Future setTargetLanguages() async { - // get a list of language models, sorted by the - // number of students who are learning that language - targetLanguages = await spaceRoom?.targetLanguages() ?? []; - setState(() {}); - } - - @override - Widget build(BuildContext context) { - if (!_initialized) return const PCircular(); - return PLoadingStatusV2( - // if we everr want it rebuild the whole thing each time (and run initState again) - // but this is computationally expensive! - // key: UniqueKey(), - shimmerChild: const ListPlaceholder(), - // onFinish: () { - // getChatAndStudentAnalytics(context); - // }, - child: SpaceAnalyticsView(this), - ); - } -} +// @override +// Widget build(BuildContext context) { +// if (!_initialized) return const PCircular(); +// return PLoadingStatusV2( +// // if we everr want it rebuild the whole thing each time (and run initState again) +// // but this is computationally expensive! +// // key: UniqueKey(), +// shimmerChild: const ListPlaceholder(), +// // onFinish: () { +// // getChatAndStudentAnalytics(context); +// // }, +// child: SpaceAnalyticsView(this), +// ); +// } +// } diff --git a/lib/pangea/pages/analytics/space_analytics/space_analytics_view.dart b/lib/pangea/pages/analytics/space_analytics/space_analytics_view.dart index c72ec3c26..02c5cb133 100644 --- a/lib/pangea/pages/analytics/space_analytics/space_analytics_view.dart +++ b/lib/pangea/pages/analytics/space_analytics/space_analytics_view.dart @@ -1,66 +1,66 @@ -import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; +// import 'package:fluffychat/widgets/matrix.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_gen/gen_l10n/l10n.dart'; -import '../base_analytics.dart'; -import 'space_analytics.dart'; +// import '../base_analytics.dart'; +// import 'space_analytics.dart'; -class SpaceAnalyticsView extends StatelessWidget { - final SpaceAnalyticsV2Controller controller; - const SpaceAnalyticsView(this.controller, {super.key}); +// class SpaceAnalyticsView extends StatelessWidget { +// final SpaceAnalyticsV2Controller controller; +// const SpaceAnalyticsView(this.controller, {super.key}); - @override - Widget build(BuildContext context) { - final String pageTitle = L10n.of(context)!.spaceAnalytics; - final TabData tab1 = TabData( - type: AnalyticsEntryType.room, - icon: Icons.chat_bubble_outline, - items: controller.chats - .map( - (room) => TabItem( - avatar: room.avatarUrl, - displayName: room.name ?? - Matrix.of(context) - .client - .getRoomById(room.roomId) - ?.getLocalizedDisplayname() ?? - "", - id: room.roomId, - ), - ) - .toList(), - ); - final TabData tab2 = TabData( - type: AnalyticsEntryType.student, - icon: Icons.people_outline, - items: controller.students - .map( - (s) => TabItem( - avatar: s.avatarUrl, - displayName: s.calcDisplayname(), - id: s.id, - ), - ) - .toList(), - ); +// @override +// Widget build(BuildContext context) { +// final String pageTitle = L10n.of(context)!.spaceAnalytics; +// final TabData tab1 = TabData( +// type: AnalyticsEntryType.room, +// icon: Icons.chat_bubble_outline, +// items: controller.chats +// .map( +// (room) => TabItem( +// avatar: room.avatarUrl, +// displayName: room.name ?? +// Matrix.of(context) +// .client +// .getRoomById(room.roomId) +// ?.getLocalizedDisplayname() ?? +// "", +// id: room.roomId, +// ), +// ) +// .toList(), +// ); +// final TabData tab2 = TabData( +// type: AnalyticsEntryType.student, +// icon: Icons.people_outline, +// items: controller.students +// .map( +// (s) => TabItem( +// avatar: s.avatarUrl, +// displayName: s.calcDisplayname(), +// id: s.id, +// ), +// ) +// .toList(), +// ); - return controller.spaceId != null - ? BaseAnalyticsPage( - selectedView: controller.widget.selectedView, - pageTitle: pageTitle, - tabs: [tab1, tab2], - alwaysSelected: AnalyticsSelected( - controller.spaceId!, - AnalyticsEntryType.space, - controller.spaceRoom?.name ?? "", - ), - defaultSelected: AnalyticsSelected( - controller.spaceId!, - AnalyticsEntryType.space, - controller.spaceRoom?.name ?? "", - ), - targetLanguages: controller.targetLanguages, - ) - : const SizedBox(); - } -} +// return controller.spaceId != null +// ? BaseAnalyticsPage( +// selectedView: controller.widget.selectedView, +// pageTitle: pageTitle, +// tabs: [tab1, tab2], +// alwaysSelected: AnalyticsSelected( +// controller.spaceId!, +// AnalyticsEntryType.space, +// controller.spaceRoom?.name ?? "", +// ), +// defaultSelected: AnalyticsSelected( +// controller.spaceId!, +// AnalyticsEntryType.space, +// controller.spaceRoom?.name ?? "", +// ), +// targetLanguages: controller.targetLanguages, +// ) +// : const SizedBox(); +// } +// } diff --git a/lib/pangea/pages/analytics/space_list/space_list.dart b/lib/pangea/pages/analytics/space_list/space_list.dart index e65bb6152..f9dbe4d31 100644 --- a/lib/pangea/pages/analytics/space_list/space_list.dart +++ b/lib/pangea/pages/analytics/space_list/space_list.dart @@ -1,100 +1,100 @@ -import 'dart:async'; +// import 'dart:async'; -import 'package:fluffychat/pangea/enum/time_span.dart'; -import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/models/language_model.dart'; -import 'package:fluffychat/pangea/pages/analytics/space_list/space_list_view.dart'; -import 'package:flutter/material.dart'; -import 'package:matrix/matrix.dart'; +// import 'package:fluffychat/pangea/enum/time_span.dart'; +// import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; +// import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; +// import 'package:fluffychat/pangea/models/language_model.dart'; +// import 'package:fluffychat/pangea/pages/analytics/space_list/space_list_view.dart'; +// import 'package:flutter/material.dart'; +// import 'package:matrix/matrix.dart'; -import '../../../../widgets/matrix.dart'; -import '../../../controllers/pangea_controller.dart'; -import '../../../utils/sync_status_util_v2.dart'; -import '../../../widgets/common/list_placeholder.dart'; +// import '../../../../widgets/matrix.dart'; +// import '../../../controllers/pangea_controller.dart'; +// import '../../../utils/sync_status_util_v2.dart'; +// import '../../../widgets/common/list_placeholder.dart'; -class AnalyticsSpaceList extends StatefulWidget { - const AnalyticsSpaceList({super.key}); +// class AnalyticsSpaceList extends StatefulWidget { +// const AnalyticsSpaceList({super.key}); - @override - State createState() => AnalyticsSpaceListController(); -} +// @override +// State createState() => AnalyticsSpaceListController(); +// } -class AnalyticsSpaceListController extends State { - PangeaController pangeaController = MatrixState.pangeaController; - List spaces = []; - StreamSubscription? stateSub; - List targetLanguages = []; +// class AnalyticsSpaceListController extends State { +// PangeaController pangeaController = MatrixState.pangeaController; +// List spaces = []; +// StreamSubscription? stateSub; +// List targetLanguages = []; - @override - void initState() { - super.initState(); - setSpaceList().then((_) => setTargetLanguages()); +// @override +// void initState() { +// super.initState(); +// setSpaceList().then((_) => setTargetLanguages()); - // reload dropdowns when their values change in analytics page - stateSub = pangeaController.analytics.stateStream.listen( - (_) => setState(() {}), - ); - } +// // reload dropdowns when their values change in analytics page +// stateSub = pangeaController.analytics.stateStream.listen( +// (_) => setState(() {}), +// ); +// } - @override - void dispose() { - stateSub?.cancel(); - super.dispose(); - } +// @override +// void dispose() { +// stateSub?.cancel(); +// super.dispose(); +// } - StreamController refreshStream = StreamController.broadcast(); +// StreamController refreshStream = StreamController.broadcast(); - Future setSpaceList() async { - final spaceList = await Matrix.of(context).client.spacesImTeaching; - spaces = spaceList - .where( - (space) => !spaceList.any( - (parentSpace) => parentSpace.spaceChildren - .any((child) => child.roomId == space.id), - ), - ) - .toList(); - setState(() {}); - } +// Future setSpaceList() async { +// final spaceList = Matrix.of(context).client.spacesImTeaching; +// spaces = spaceList +// .where( +// (space) => !spaceList.any( +// (parentSpace) => parentSpace.spaceChildren +// .any((child) => child.roomId == space.id), +// ), +// ) +// .toList(); +// setState(() {}); +// } - Future setTargetLanguages() async { - if (spaces.isEmpty) return; - final Map langCounts = {}; - for (final Room space in spaces) { - final List targetLangs = await space.targetLanguages(); - for (final LanguageModel lang in targetLangs) { - langCounts[lang] ??= 0; - langCounts[lang] = langCounts[lang]! + 1; - } - } - targetLanguages = langCounts.entries.map((entry) => entry.key).toList() - ..sort( - (a, b) => langCounts[b]!.compareTo(langCounts[a]!), - ); - setState(() {}); - } +// Future setTargetLanguages() async { +// if (spaces.isEmpty) return; +// final Map langCounts = {}; +// for (final Room space in spaces) { +// final List targetLangs = await space.targetLanguages(); +// for (final LanguageModel lang in targetLangs) { +// langCounts[lang] ??= 0; +// langCounts[lang] = langCounts[lang]! + 1; +// } +// } +// targetLanguages = langCounts.entries.map((entry) => entry.key).toList() +// ..sort( +// (a, b) => langCounts[b]!.compareTo(langCounts[a]!), +// ); +// setState(() {}); +// } - void toggleTimeSpan(BuildContext context, TimeSpan timeSpan) { - pangeaController.analytics.setCurrentAnalyticsTimeSpan(timeSpan); - refreshStream.add(false); - setState(() {}); - } +// void toggleTimeSpan(BuildContext context, TimeSpan timeSpan) { +// pangeaController.analytics.setCurrentAnalyticsTimeSpan(timeSpan); +// refreshStream.add(false); +// setState(() {}); +// } - Future toggleSpaceLang(LanguageModel lang) async { - await pangeaController.analytics.setCurrentAnalyticsLang(lang); - refreshStream.add(false); - setState(() {}); - } +// Future toggleSpaceLang(LanguageModel lang) async { +// await pangeaController.analytics.setCurrentAnalyticsLang(lang); +// refreshStream.add(false); +// setState(() {}); +// } - @override - Widget build(BuildContext context) { - return PLoadingStatusV2( - shimmerChild: const ListPlaceholder(), - child: AnalyticsSpaceListView(this), - onFinish: () { - // getAllClassAnalytics(context); - }, - ); - } -} +// @override +// Widget build(BuildContext context) { +// return PLoadingStatusV2( +// shimmerChild: const ListPlaceholder(), +// child: AnalyticsSpaceListView(this), +// onFinish: () { +// // getAllClassAnalytics(context); +// }, +// ); +// } +// } diff --git a/lib/pangea/pages/analytics/space_list/space_list_view.dart b/lib/pangea/pages/analytics/space_list/space_list_view.dart index 7ef5fb45e..c9b63a62c 100644 --- a/lib/pangea/pages/analytics/space_list/space_list_view.dart +++ b/lib/pangea/pages/analytics/space_list/space_list_view.dart @@ -1,89 +1,89 @@ -import 'package:fluffychat/pangea/enum/time_span.dart'; -import 'package:fluffychat/pangea/pages/analytics/analytics_language_button.dart'; -import 'package:fluffychat/pangea/pages/analytics/analytics_list_tile.dart'; -import 'package:fluffychat/pangea/pages/analytics/time_span_menu_button.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; +// import 'package:fluffychat/pangea/enum/time_span.dart'; +// import 'package:fluffychat/pangea/pages/analytics/analytics_language_button.dart'; +// import 'package:fluffychat/pangea/pages/analytics/analytics_list_tile.dart'; +// import 'package:fluffychat/pangea/pages/analytics/time_span_menu_button.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_gen/gen_l10n/l10n.dart'; +// import 'package:go_router/go_router.dart'; -import '../base_analytics.dart'; -import 'space_list.dart'; +// import '../base_analytics.dart'; +// import 'space_list.dart'; -class AnalyticsSpaceListView extends StatelessWidget { - final AnalyticsSpaceListController controller; - const AnalyticsSpaceListView(this.controller, {super.key}); +// class AnalyticsSpaceListView extends StatelessWidget { +// final AnalyticsSpaceListController controller; +// const AnalyticsSpaceListView(this.controller, {super.key}); - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - centerTitle: true, - title: Text( - L10n.of(context)!.spaceAnalytics, - style: TextStyle( - color: Theme.of(context).textTheme.bodyLarge!.color, - fontSize: 18, - fontWeight: FontWeight.w700, - ), - overflow: TextOverflow.clip, - textAlign: TextAlign.center, - ), - leading: IconButton( - icon: const Icon(Icons.close_outlined), - onPressed: () => context.pop(), - ), - ), - body: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - TimeSpanMenuButton( - value: controller - .pangeaController.analytics.currentAnalyticsTimeSpan, - onChange: (TimeSpan value) => controller.toggleTimeSpan( - context, - value, - ), - ), - AnalyticsLanguageButton( - value: - controller.pangeaController.analytics.currentAnalyticsLang, - onChange: (lang) => controller.toggleSpaceLang(lang), - languages: - controller.pangeaController.pLanguageStore.targetOptions, - ), - ], - ), - Flexible( - child: ListView.builder( - itemCount: controller.spaces.length, - itemBuilder: (context, i) => AnalyticsListTile( - defaultSelected: AnalyticsSelected( - controller.spaces[i].id, - AnalyticsEntryType.space, - controller.spaces[i].name, - ), - avatar: controller.spaces[i].avatar, - selected: AnalyticsSelected( - controller.spaces[i].id, - AnalyticsEntryType.space, - controller.spaces[i].name, - ), - onTap: (selected) { - context.go( - '/rooms/analytics/${selected.id}', - ); - }, - allowNavigateOnSelect: true, - isSelected: false, - pangeaController: controller.pangeaController, - refreshStream: controller.refreshStream, - ), - ), - ), - ], - ), - ); - } -} +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// appBar: AppBar( +// centerTitle: true, +// title: Text( +// L10n.of(context)!.spaceAnalytics, +// style: TextStyle( +// color: Theme.of(context).textTheme.bodyLarge!.color, +// fontSize: 18, +// fontWeight: FontWeight.w700, +// ), +// overflow: TextOverflow.clip, +// textAlign: TextAlign.center, +// ), +// leading: IconButton( +// icon: const Icon(Icons.close_outlined), +// onPressed: () => context.pop(), +// ), +// ), +// body: Column( +// children: [ +// Row( +// mainAxisAlignment: MainAxisAlignment.spaceEvenly, +// children: [ +// TimeSpanMenuButton( +// value: controller +// .pangeaController.analytics.currentAnalyticsTimeSpan, +// onChange: (TimeSpan value) => controller.toggleTimeSpan( +// context, +// value, +// ), +// ), +// AnalyticsLanguageButton( +// value: +// controller.pangeaController.analytics.currentAnalyticsLang, +// onChange: (lang) => controller.toggleSpaceLang(lang), +// languages: +// controller.pangeaController.pLanguageStore.targetOptions, +// ), +// ], +// ), +// Flexible( +// child: ListView.builder( +// itemCount: controller.spaces.length, +// itemBuilder: (context, i) => AnalyticsListTile( +// defaultSelected: AnalyticsSelected( +// controller.spaces[i].id, +// AnalyticsEntryType.space, +// controller.spaces[i].name, +// ), +// avatar: controller.spaces[i].avatar, +// selected: AnalyticsSelected( +// controller.spaces[i].id, +// AnalyticsEntryType.space, +// controller.spaces[i].name, +// ), +// onTap: (selected) { +// context.go( +// '/rooms/analytics/${selected.id}', +// ); +// }, +// allowNavigateOnSelect: true, +// isSelected: false, +// pangeaController: controller.pangeaController, +// refreshStream: controller.refreshStream, +// ), +// ), +// ), +// ], +// ), +// ); +// } +// } diff --git a/lib/pangea/pages/analytics/student_analytics/student_analytics.dart b/lib/pangea/pages/analytics/student_analytics/student_analytics.dart index 007a04d93..3a5309e5e 100644 --- a/lib/pangea/pages/analytics/student_analytics/student_analytics.dart +++ b/lib/pangea/pages/analytics/student_analytics/student_analytics.dart @@ -1,90 +1,90 @@ -import 'dart:developer'; +// import 'dart:developer'; -import 'package:fluffychat/pangea/constants/language_constants.dart'; -import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; -import 'package:fluffychat/pangea/enum/bar_chart_view_enum.dart'; -import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/models/language_model.dart'; -import 'package:fluffychat/pangea/widgets/common/list_placeholder.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:matrix/matrix.dart'; +// import 'package:fluffychat/pangea/constants/language_constants.dart'; +// import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; +// import 'package:fluffychat/pangea/enum/bar_chart_view_enum.dart'; +// import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; +// import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; +// import 'package:fluffychat/pangea/models/language_model.dart'; +// import 'package:fluffychat/pangea/widgets/common/list_placeholder.dart'; +// import 'package:flutter/foundation.dart'; +// import 'package:flutter/material.dart'; +// import 'package:matrix/matrix.dart'; -import '../../../../widgets/matrix.dart'; -import '../../../controllers/pangea_controller.dart'; -import '../../../utils/sync_status_util_v2.dart'; -import '../base_analytics.dart'; -import 'student_analytics_view.dart'; +// import '../../../../widgets/matrix.dart'; +// import '../../../controllers/pangea_controller.dart'; +// import '../../../utils/sync_status_util_v2.dart'; +// import '../base_analytics.dart'; +// import 'student_analytics_view.dart'; -class StudentAnalyticsPage extends StatefulWidget { - final BarChartViewSelection selectedView; - const StudentAnalyticsPage({super.key, required this.selectedView}); +// class StudentAnalyticsPage extends StatefulWidget { +// final BarChartViewSelection selectedView; +// const StudentAnalyticsPage({super.key, required this.selectedView}); - @override - State createState() => StudentAnalyticsController(); -} +// @override +// State createState() => StudentAnalyticsController(); +// } -class StudentAnalyticsController extends State { - final PangeaController _pangeaController = MatrixState.pangeaController; - AnalyticsSelected? selected; +// class StudentAnalyticsController extends State { +// final PangeaController _pangeaController = MatrixState.pangeaController; +// AnalyticsSelected? selected; - @override - void initState() { - super.initState(); - } +// @override +// void initState() { +// super.initState(); +// } - @override - void dispose() { - super.dispose(); - } +// @override +// void dispose() { +// super.dispose(); +// } - List _chats = []; - List get chats { - if (_chats.isEmpty) { - _pangeaController.matrixState.client.chatsImAStudentIn.then((result) { - setState(() => _chats = result); - }); - } - return _chats; - } +// List _chats = []; +// List get chats { +// if (_chats.isEmpty) { +// _pangeaController.matrixState.client.chatsImAStudentIn.then((result) { +// setState(() => _chats = result); +// }); +// } +// return _chats; +// } - List get spaces => - _pangeaController.matrixState.client.spacesImAStudentIn; +// List get spaces => +// _pangeaController.matrixState.client.spacesImAStudentIn; - String? get userId { - final id = _pangeaController.matrixState.client.userID; - debugger(when: kDebugMode && id == null); - return id; - } +// String? get userId { +// final id = _pangeaController.matrixState.client.userID; +// debugger(when: kDebugMode && id == null); +// return id; +// } - List get targetLanguages { - final LanguageModel? l2 = - _pangeaController.languageController.activeL2Model(); - final List analyticsRoomLangs = - _pangeaController.matrixState.client.allMyAnalyticsRooms - .map((analyticsRoom) => analyticsRoom.madeForLang) - .where((langCode) => langCode != null) - .map((langCode) => PangeaLanguage.byLangCode(langCode!)) - .where( - (langModel) => langModel.langCode != LanguageKeys.unknownLanguage, - ) - .toList(); - if (l2 != null) { - analyticsRoomLangs.add(l2); - } - return analyticsRoomLangs.toSet().toList(); - } +// List get targetLanguages { +// final LanguageModel? l2 = +// _pangeaController.languageController.activeL2Model(); +// final List analyticsRoomLangs = +// _pangeaController.matrixState.client.allMyAnalyticsRooms +// .map((analyticsRoom) => analyticsRoom.madeForLang) +// .where((langCode) => langCode != null) +// .map((langCode) => PangeaLanguage.byLangCode(langCode!)) +// .where( +// (langModel) => langModel.langCode != LanguageKeys.unknownLanguage, +// ) +// .toList(); +// if (l2 != null) { +// analyticsRoomLangs.add(l2); +// } +// return analyticsRoomLangs.toSet().toList(); +// } - @override - Widget build(BuildContext context) { - return PLoadingStatusV2( - // if we everr want it rebuild the whole thing each time (and run initState again) - // but this is computationally expensive! - // key: UniqueKey(), - shimmerChild: const ListPlaceholder(), - // onFinish: initialize, - child: StudentAnalyticsView(this), - ); - } -} +// @override +// Widget build(BuildContext context) { +// return PLoadingStatusV2( +// // if we everr want it rebuild the whole thing each time (and run initState again) +// // but this is computationally expensive! +// // key: UniqueKey(), +// shimmerChild: const ListPlaceholder(), +// // onFinish: initialize, +// child: StudentAnalyticsView(this), +// ); +// } +// } diff --git a/lib/pangea/pages/analytics/student_analytics/student_analytics_view.dart b/lib/pangea/pages/analytics/student_analytics/student_analytics_view.dart index 6ea754891..a778f35d8 100644 --- a/lib/pangea/pages/analytics/student_analytics/student_analytics_view.dart +++ b/lib/pangea/pages/analytics/student_analytics/student_analytics_view.dart @@ -1,66 +1,66 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_gen/gen_l10n/l10n.dart'; -import '../../../../utils/matrix_sdk_extensions/matrix_locals.dart'; -import '../base_analytics.dart'; -import 'student_analytics.dart'; +// import '../../../../utils/matrix_sdk_extensions/matrix_locals.dart'; +// import '../base_analytics.dart'; +// import 'student_analytics.dart'; -class StudentAnalyticsView extends StatelessWidget { - final StudentAnalyticsController controller; - const StudentAnalyticsView(this.controller, {super.key}); +// class StudentAnalyticsView extends StatelessWidget { +// final StudentAnalyticsController controller; +// const StudentAnalyticsView(this.controller, {super.key}); - @override - Widget build(BuildContext context) { - final String pageTitle = L10n.of(context)!.myLearning; - final TabData chatTabData = TabData( - type: AnalyticsEntryType.room, - icon: Icons.chat_bubble_outline, - items: (controller.chats) - .map( - (c) => TabItem( - avatar: c.avatar, - displayName: - c.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), - id: c.id, - ), - ) - .toList(), - allowNavigateOnSelect: false, - ); - final TabData classTabData = TabData( - type: AnalyticsEntryType.space, - icon: Icons.workspaces, - items: (controller.spaces ?? []) - .map( - (c) => TabItem( - avatar: c.avatar, - displayName: - c.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), - id: c.id, - ), - ) - .toList(), - allowNavigateOnSelect: false, - ); +// @override +// Widget build(BuildContext context) { +// final String pageTitle = L10n.of(context)!.myLearning; +// final TabData chatTabData = TabData( +// type: AnalyticsEntryType.room, +// icon: Icons.chat_bubble_outline, +// items: (controller.chats) +// .map( +// (c) => TabItem( +// avatar: c.avatar, +// displayName: +// c.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), +// id: c.id, +// ), +// ) +// .toList(), +// allowNavigateOnSelect: false, +// ); +// final TabData classTabData = TabData( +// type: AnalyticsEntryType.space, +// icon: Icons.workspaces, +// items: (controller.spaces ?? []) +// .map( +// (c) => TabItem( +// avatar: c.avatar, +// displayName: +// c.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), +// id: c.id, +// ), +// ) +// .toList(), +// allowNavigateOnSelect: false, +// ); - return controller.userId != null - ? BaseAnalyticsPage( - selectedView: controller.widget.selectedView, - pageTitle: pageTitle, - tabs: [chatTabData, classTabData], - alwaysSelected: AnalyticsSelected( - controller.userId!, - AnalyticsEntryType.student, - L10n.of(context)!.allChatsAndClasses, - ), - myAnalyticsController: controller, - defaultSelected: AnalyticsSelected( - controller.userId!, - AnalyticsEntryType.student, - L10n.of(context)!.allChatsAndClasses, - ), - targetLanguages: controller.targetLanguages, - ) - : const SizedBox(); - } -} +// return controller.userId != null +// ? BaseAnalyticsPage( +// selectedView: controller.widget.selectedView, +// pageTitle: pageTitle, +// tabs: [chatTabData, classTabData], +// alwaysSelected: AnalyticsSelected( +// controller.userId!, +// AnalyticsEntryType.student, +// L10n.of(context)!.allChatsAndClasses, +// ), +// myAnalyticsController: controller, +// defaultSelected: AnalyticsSelected( +// controller.userId!, +// AnalyticsEntryType.student, +// L10n.of(context)!.allChatsAndClasses, +// ), +// targetLanguages: controller.targetLanguages, +// ) +// : const SizedBox(); +// } +// } diff --git a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart new file mode 100644 index 000000000..ae6b13e30 --- /dev/null +++ b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart @@ -0,0 +1,195 @@ +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; +import 'package:fluffychat/pangea/enum/progress_indicators_enum.dart'; +import 'package:fluffychat/pangea/enum/time_span.dart'; +import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart'; +import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart'; +import 'package:fluffychat/utils/string_color.dart'; +import 'package:fluffychat/widgets/avatar.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; + +/// A summary of "My Analytics" shown at the top of the chat list +/// It shows a variety of progress indicators such as +/// messages sent, words used, and error types, which can +/// be clicked to access more fine-grained analytics data. +class LearningProgressIndicators extends StatefulWidget { + const LearningProgressIndicators({ + super.key, + }); + + @override + LearningProgressIndicatorsState createState() => + LearningProgressIndicatorsState(); +} + +class LearningProgressIndicatorsState + extends State { + final PangeaController _pangeaController = MatrixState.pangeaController; + int? wordsUsed; + int? errorTypes; + + @override + void initState() { + super.initState(); + setData(); + } + + AnalyticsSelected get defaultSelected => AnalyticsSelected( + _pangeaController.matrixState.client.userID!, + AnalyticsEntryType.student, + "", + ); + + Future setData() async { + await getNumLemmasUsed(); + setState(() {}); + } + + Future getNumLemmasUsed() async { + final constructs = await _pangeaController.analytics.getConstructs( + defaultSelected: defaultSelected, + timeSpan: TimeSpan.forever, + ); + if (constructs == null) { + errorTypes = 0; + wordsUsed = 0; + return; + } + + final List errorLemmas = []; + final List vocabLemmas = []; + for (final event in constructs) { + for (final use in event.content.uses) { + if (use.lemma == null) continue; + switch (use.constructType) { + case ConstructTypeEnum.grammar: + errorLemmas.add(use.lemma!); + break; + case ConstructTypeEnum.vocab: + vocabLemmas.add(use.lemma!); + break; + default: + break; + } + } + } + errorTypes = errorLemmas.toSet().length; + wordsUsed = vocabLemmas.toSet().length; + } + + int? getProgressPoints(ProgressIndicatorEnum indicator) { + switch (indicator) { + case ProgressIndicatorEnum.wordsUsed: + return wordsUsed; + case ProgressIndicatorEnum.errorTypes: + return errorTypes; + case ProgressIndicatorEnum.level: + return level; + } + } + + int get xpPoints { + final points = [ + wordsUsed ?? 0, + errorTypes ?? 0, + ]; + return points.reduce((a, b) => a + b); + } + + int get level => xpPoints ~/ 100; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 36, + vertical: 16, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + FutureBuilder( + future: + _pangeaController.matrixState.client.getProfileFromUserId( + _pangeaController.matrixState.client.userID!, + ), + builder: (context, snapshot) { + final mxid = Matrix.of(context).client.userID ?? + L10n.of(context)!.user; + return Avatar( + name: snapshot.data?.displayName ?? mxid.localpart ?? mxid, + mxContent: snapshot.data?.avatarUrl, + ); + }, + ), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: ProgressIndicatorEnum.values + .where( + (indicator) => indicator != ProgressIndicatorEnum.level, + ) + .map( + (indicator) => ProgressIndicatorBadge( + points: getProgressPoints(indicator), + onTap: () {}, + progressIndicator: indicator, + ), + ) + .toList(), + ), + ), + ], + ), + const SizedBox(height: 4), + SizedBox( + height: 35, + child: Stack( + alignment: Alignment.centerLeft, + children: [ + Positioned( + right: 0, + child: Row( + children: [ + SizedBox( + width: FluffyThemes.columnWidth - (36 * 2) - 25, + child: LinearProgressIndicator( + value: (xpPoints % 100) / 100, + color: Theme.of(context).colorScheme.primary, + backgroundColor: + Theme.of(context).colorScheme.onPrimary, + minHeight: 15, + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + ), + ), + ], + ), + ), + Positioned( + left: 0, + child: CircleAvatar( + backgroundColor: "$level $xpPoints".lightColorAvatar, + radius: 16, + child: Text( + "$level", + style: const TextStyle(color: Colors.white), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart b/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart new file mode 100644 index 000000000..93e932aa2 --- /dev/null +++ b/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart @@ -0,0 +1,51 @@ +import 'package:fluffychat/pangea/enum/progress_indicators_enum.dart'; +import 'package:flutter/material.dart'; + +/// A badge that represents one learning progress indicator (i.e., construct uses) +class ProgressIndicatorBadge extends StatelessWidget { + final int? points; + final VoidCallback onTap; + final ProgressIndicatorEnum progressIndicator; + + const ProgressIndicatorBadge({ + super.key, + required this.points, + required this.onTap, + required this.progressIndicator, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + child: Tooltip( + message: progressIndicator.tooltip(context), + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + progressIndicator.icon, + color: progressIndicator.color(context), + ), + const SizedBox(width: 5), + points != null + ? Text( + points.toString(), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ) + : const CircularProgressIndicator.adaptive(), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/utils/client_manager.dart b/lib/utils/client_manager.dart index 808dfd9fc..5ea52f5e5 100644 --- a/lib/utils/client_manager.dart +++ b/lib/utils/client_manager.dart @@ -139,7 +139,6 @@ abstract class ClientManager { timeline: StateFilter( notTypes: [ PangeaEventTypes.construct, - PangeaEventTypes.summaryAnalytics, ], ), ),