merge conflicts
This commit is contained in:
commit
8a01644f6d
20 changed files with 489 additions and 359 deletions
|
|
@ -4010,9 +4010,9 @@
|
|||
"wordsPerMinute": "Words per minute",
|
||||
"autoIGCToolName": "Run Language Assistance Automatically",
|
||||
"autoIGCToolDescription": "Automatically run language assistance after typing messages",
|
||||
"runGrammarCorrection": "Run grammar correction",
|
||||
"runGrammarCorrection": "Check message",
|
||||
"grammarCorrectionFailed": "Issues to address",
|
||||
"grammarCorrectionComplete": "Grammar correction complete",
|
||||
"grammarCorrectionComplete": "Looks good!",
|
||||
"leaveRoomDescription": "The chat will be moved to the archive. Other users will be able to see that you have left the chat.",
|
||||
"archiveSpaceDescription": "All chats within this space will be moved to the archive for yourself and other non-admin users.",
|
||||
"leaveSpaceDescription": "All chats within this space will be moved to the archive. Other users will be able to see that you have left the space.",
|
||||
|
|
|
|||
|
|
@ -4609,9 +4609,9 @@
|
|||
"enterNumber": "Introduzca un valor numérico entero.",
|
||||
"autoIGCToolName": "Ejecutar automáticamente la asistencia lingüística",
|
||||
"autoIGCToolDescription": "Ejecutar automáticamente la asistencia lingüística después de escribir mensajes",
|
||||
"runGrammarCorrection": "Corregir la gramática",
|
||||
"runGrammarCorrection": "Comprobar mensaje",
|
||||
"grammarCorrectionFailed": "Cuestiones a tratar",
|
||||
"grammarCorrectionComplete": "Corrección gramatical completa",
|
||||
"grammarCorrectionComplete": "¡Se ve bien!",
|
||||
"leaveRoomDescription": "El chat se moverá al archivo. Los demás usuarios podrán ver que has abandonado el chat.",
|
||||
"archiveSpaceDescription": "Todos los chats de este espacio se moverán al archivo para ti y otros usuarios que no sean administradores.",
|
||||
"leaveSpaceDescription": "Todos los chats dentro de este espacio se moverán al archivo. Los demás usuarios podrán ver que has abandonado el espacio.",
|
||||
|
|
|
|||
|
|
@ -35,11 +35,26 @@ class ChatPermissionsSettingsView extends StatelessWidget {
|
|||
final powerLevels = Map<String, dynamic>.from(powerLevelsContent)
|
||||
// #Pangea
|
||||
// ..removeWhere((k, v) => v is! int);
|
||||
..removeWhere((k, v) => v is! int || k.equals("m.call.invite"));
|
||||
..removeWhere(
|
||||
(k, v) =>
|
||||
v is! int ||
|
||||
k.equals("m.call.invite") ||
|
||||
k.equals("historical") ||
|
||||
k.equals("state_default"),
|
||||
);
|
||||
// Pangea#
|
||||
final eventsPowerLevels = Map<String, int?>.from(
|
||||
powerLevelsContent.tryGetMap<String, int?>('events') ?? {},
|
||||
)..removeWhere((k, v) => v is! int);
|
||||
// #Pangea
|
||||
)..removeWhere(
|
||||
(k, v) =>
|
||||
v is! int ||
|
||||
k.equals("m.space.child") ||
|
||||
k.equals("pangea.usranalytics") ||
|
||||
k.equals(EventTypes.RoomPowerLevels),
|
||||
);
|
||||
// )..removeWhere((k, v) => v is! int);
|
||||
// Pangea#
|
||||
return Column(
|
||||
children: [
|
||||
Column(
|
||||
|
|
@ -57,51 +72,59 @@ class ChatPermissionsSettingsView extends StatelessWidget {
|
|||
),
|
||||
canEdit: room.canChangePowerLevel,
|
||||
),
|
||||
Divider(color: Theme.of(context).dividerColor),
|
||||
ListTile(
|
||||
title: Text(
|
||||
L10n.of(context)!.notifications,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
const key = 'rooms';
|
||||
final value = powerLevelsContent
|
||||
.containsKey('notifications')
|
||||
? powerLevelsContent
|
||||
.tryGetMap<String, Object?>('notifications')
|
||||
?.tryGet<int>('rooms') ??
|
||||
0
|
||||
: 0;
|
||||
return PermissionsListTile(
|
||||
permissionKey: key,
|
||||
permission: value,
|
||||
category: 'notifications',
|
||||
canEdit: room.canChangePowerLevel,
|
||||
onChanged: (level) => controller.editPowerLevel(
|
||||
context,
|
||||
key,
|
||||
value,
|
||||
newLevel: level,
|
||||
category: 'notifications',
|
||||
// #Pangea
|
||||
// Why would teacher need to stop students from seeing notifications?
|
||||
// Divider(color: Theme.of(context).dividerColor),
|
||||
// ListTile(
|
||||
// title: Text(
|
||||
// L10n.of(context)!.notifications,
|
||||
// style: TextStyle(
|
||||
// color: Theme.of(context).colorScheme.primary,
|
||||
// fontWeight: FontWeight.bold,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Builder(
|
||||
// builder: (context) {
|
||||
// const key = 'rooms';
|
||||
// final value = powerLevelsContent
|
||||
// .containsKey('notifications')
|
||||
// ? powerLevelsContent
|
||||
// .tryGetMap<String, Object?>('notifications')
|
||||
// ?.tryGet<int>('rooms') ??
|
||||
// 0
|
||||
// : 0;
|
||||
// return PermissionsListTile(
|
||||
// permissionKey: key,
|
||||
// permission: value,
|
||||
// category: 'notifications',
|
||||
// canEdit: room.canChangePowerLevel,
|
||||
// onChanged: (level) => controller.editPowerLevel(
|
||||
// context,
|
||||
// key,
|
||||
// value,
|
||||
// newLevel: level,
|
||||
// category: 'notifications',
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// Only show if there are actually items in this category
|
||||
if (eventsPowerLevels.isNotEmpty)
|
||||
// Pangea#
|
||||
Divider(color: Theme.of(context).dividerColor),
|
||||
// #Pangea
|
||||
if (eventsPowerLevels.isNotEmpty)
|
||||
// Pangea#
|
||||
ListTile(
|
||||
title: Text(
|
||||
L10n.of(context)!.configureChat,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Divider(color: Theme.of(context).dividerColor),
|
||||
ListTile(
|
||||
title: Text(
|
||||
L10n.of(context)!.configureChat,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
for (final entry in eventsPowerLevels.entries)
|
||||
PermissionsListTile(
|
||||
permissionKey: entry.key,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import 'package:fluffychat/pangea/choreographer/controllers/message_options.dart
|
|||
import 'package:fluffychat/pangea/constants/language_keys.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
|
||||
import 'package:fluffychat/pangea/enum/assistance_state_enum.dart';
|
||||
import 'package:fluffychat/pangea/enum/edit_type.dart';
|
||||
import 'package:fluffychat/pangea/models/it_step.dart';
|
||||
import 'package:fluffychat/pangea/models/language_detection_model.dart';
|
||||
|
|
@ -570,13 +571,3 @@ class Choreographer {
|
|||
return AssistanceState.complete;
|
||||
}
|
||||
}
|
||||
|
||||
// assistance state is, user has not typed a message, user has typed a message and IGC has not run,
|
||||
// IGC is running, IGC has run and there are remaining steps (either IT or IGC), or all steps are done
|
||||
enum AssistanceState {
|
||||
noMessage,
|
||||
notFetched,
|
||||
fetching,
|
||||
fetched,
|
||||
complete,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
||||
import 'package:fluffychat/pangea/constants/colors.dart';
|
||||
import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
|
||||
import 'package:fluffychat/pangea/enum/assistance_state_enum.dart';
|
||||
import 'package:fluffychat/pangea/widgets/user_settings/p_language_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
|
@ -54,15 +53,15 @@ class StartIGCButtonState extends State<StartIGCButton>
|
|||
setState(() => prevState = assistanceState);
|
||||
}
|
||||
|
||||
bool get itEnabled => widget.controller.choreographer.itEnabled;
|
||||
bool get igcEnabled => widget.controller.choreographer.igcEnabled;
|
||||
CanSendStatus get canSendStatus =>
|
||||
widget.controller.pangeaController.subscriptionController.canSendStatus;
|
||||
bool get grammarCorrectionEnabled =>
|
||||
(itEnabled || igcEnabled) && canSendStatus == CanSendStatus.subscribed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool itEnabled = widget.controller.choreographer.itEnabled;
|
||||
final bool igcEnabled = widget.controller.choreographer.igcEnabled;
|
||||
final CanSendStatus canSendStatus =
|
||||
widget.controller.pangeaController.subscriptionController.canSendStatus;
|
||||
final bool grammarCorrectionEnabled =
|
||||
(itEnabled || igcEnabled) && canSendStatus == CanSendStatus.subscribed;
|
||||
|
||||
if (!grammarCorrectionEnabled ||
|
||||
widget.controller.choreographer.isAutoIGCEnabled ||
|
||||
widget.controller.choreographer.choreoMode == ChoreoMode.it) {
|
||||
|
|
@ -89,7 +88,7 @@ class StartIGCButtonState extends State<StartIGCButton>
|
|||
disabledElevation: 0,
|
||||
shape: const CircleBorder(),
|
||||
onPressed: () {
|
||||
if (assistanceState != AssistanceState.complete) {
|
||||
if (assistanceState != AssistanceState.fetching) {
|
||||
widget.controller.choreographer
|
||||
.getLanguageHelp(
|
||||
false,
|
||||
|
|
@ -142,32 +141,3 @@ class StartIGCButtonState extends State<StartIGCButton>
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension AssistanceStateExtension on AssistanceState {
|
||||
Color stateColor(context) {
|
||||
switch (this) {
|
||||
case AssistanceState.noMessage:
|
||||
case AssistanceState.notFetched:
|
||||
case AssistanceState.fetching:
|
||||
return Theme.of(context).colorScheme.primary;
|
||||
case AssistanceState.fetched:
|
||||
return PangeaColors.igcError;
|
||||
case AssistanceState.complete:
|
||||
return AppConfig.success;
|
||||
}
|
||||
}
|
||||
|
||||
String tooltip(L10n l10n) {
|
||||
switch (this) {
|
||||
case AssistanceState.noMessage:
|
||||
case AssistanceState.notFetched:
|
||||
return l10n.runGrammarCorrection;
|
||||
case AssistanceState.fetching:
|
||||
return "";
|
||||
case AssistanceState.fetched:
|
||||
return l10n.grammarCorrectionFailed;
|
||||
case AssistanceState.complete:
|
||||
return l10n.grammarCorrectionComplete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class PangeaLanguage {
|
|||
|
||||
static Future<void> initialize() async {
|
||||
try {
|
||||
_langList = await _getCahedFlags();
|
||||
_langList = await _getCachedFlags();
|
||||
if (await _shouldFetch || _langList.isEmpty) {
|
||||
_langList = await LanguageRepo.fetchLanguages();
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ class PangeaLanguage {
|
|||
await MyShared.saveJson(PrefKey.flags, flagMap);
|
||||
}
|
||||
|
||||
static Future<List<LanguageModel>> _getCahedFlags() async {
|
||||
static Future<List<LanguageModel>> _getCachedFlags() async {
|
||||
final Map<dynamic, dynamic>? flagsMap =
|
||||
await MyShared.readJson(PrefKey.flags);
|
||||
if (flagsMap == null) {
|
||||
|
|
|
|||
|
|
@ -25,20 +25,23 @@ class MyAnalyticsController extends BaseController {
|
|||
final int _maxMessagesCached = 10;
|
||||
final int _minutesBeforeUpdate = 5;
|
||||
|
||||
/// the time since the last update that will trigger an automatic update
|
||||
final Duration _timeSinceUpdate = const Duration(days: 1);
|
||||
|
||||
MyAnalyticsController(PangeaController pangeaController) {
|
||||
_pangeaController = pangeaController;
|
||||
}
|
||||
|
||||
// adds the listener that handles when to run automatic updates
|
||||
// to analytics - either after a certain number of messages sent/
|
||||
// received or after a certain amount of time without an update
|
||||
/// adds the listener that handles when to run automatic updates
|
||||
/// to analytics - either after a certain number of messages sent/
|
||||
/// received or after a certain amount of time [_timeSinceUpdate] without an update
|
||||
Future<void> addEventsListener() async {
|
||||
final Client client = _pangeaController.matrixState.client;
|
||||
|
||||
// if analytics haven't been updated in the last day, update them
|
||||
DateTime? lastUpdated = await _pangeaController.analytics
|
||||
.myAnalyticsLastUpdated(PangeaEventTypes.summaryAnalytics);
|
||||
final DateTime yesterday = DateTime.now().subtract(const Duration(days: 1));
|
||||
final DateTime yesterday = DateTime.now().subtract(_timeSinceUpdate);
|
||||
if (lastUpdated?.isBefore(yesterday) ?? true) {
|
||||
debugPrint("analytics out-of-date, updating");
|
||||
await updateAnalytics();
|
||||
|
|
@ -53,9 +56,9 @@ class MyAnalyticsController extends BaseController {
|
|||
});
|
||||
}
|
||||
|
||||
// given an update from sync stream, check if the update contains
|
||||
// messages for which analytics will be saved. If so, reset the timer
|
||||
// and add the event ID to the cache of un-added event IDs
|
||||
/// given an update from sync stream, check if the update contains
|
||||
/// messages for which analytics will be saved. If so, reset the timer
|
||||
/// and add the event ID to the cache of un-added event IDs
|
||||
void updateAnalyticsTimer(SyncUpdate update, DateTime? lastUpdated) {
|
||||
for (final entry in update.rooms!.join!.entries) {
|
||||
final Room room =
|
||||
|
|
@ -160,6 +163,7 @@ class MyAnalyticsController extends BaseController {
|
|||
_updateCompleter = Completer<void>();
|
||||
try {
|
||||
await _updateAnalytics();
|
||||
clearMessagesSinceUpdate();
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
|
|
@ -172,6 +176,9 @@ class MyAnalyticsController extends BaseController {
|
|||
}
|
||||
}
|
||||
|
||||
// top level analytics sending function. Send analytics
|
||||
// for each type of analytics event
|
||||
// to each of the applicable analytics rooms
|
||||
Future<void> _updateAnalytics() async {
|
||||
// if the user's l2 is not sent, don't send analytics
|
||||
final String? userL2 = _pangeaController.languageController.activeL2Code();
|
||||
|
|
@ -179,11 +186,6 @@ class MyAnalyticsController extends BaseController {
|
|||
return;
|
||||
}
|
||||
|
||||
// top level analytics sending function. Send analytics
|
||||
// for each type of analytics event
|
||||
// to each of the applicable analytics rooms
|
||||
clearMessagesSinceUpdate();
|
||||
|
||||
// fetch a list of all the chats that the user is studying
|
||||
// and a list of all the spaces in which the user is studying
|
||||
await setStudentChats();
|
||||
|
|
@ -199,9 +201,21 @@ class MyAnalyticsController extends BaseController {
|
|||
.where((lastUpdate) => lastUpdate != null)
|
||||
.cast<DateTime>()
|
||||
.toList();
|
||||
lastUpdates.sort((a, b) => a.compareTo(b));
|
||||
final DateTime? leastRecentUpdate =
|
||||
lastUpdates.isNotEmpty ? lastUpdates.first : null;
|
||||
|
||||
/// Get the last time that analytics to for current target language
|
||||
/// were updated. This my present a problem is the user has analytics
|
||||
/// rooms for multiple languages, and a non-target language was updated
|
||||
/// less recently than the target language. In this case, some data may
|
||||
/// be missing, but a case like that seems relatively rare, and could
|
||||
/// result in unnecessaily going too far back in the chat history
|
||||
DateTime? l2AnalyticsLastUpdated = lastUpdatedMap[userL2];
|
||||
if (l2AnalyticsLastUpdated == null) {
|
||||
/// if the target language has never been updated, use the least
|
||||
/// recent update time
|
||||
lastUpdates.sort((a, b) => a.compareTo(b));
|
||||
l2AnalyticsLastUpdated =
|
||||
lastUpdates.isNotEmpty ? lastUpdates.first : null;
|
||||
}
|
||||
|
||||
// for each chat the user is studying in, get all the messages
|
||||
// since the least recent update analytics update, and sort them
|
||||
|
|
@ -209,7 +223,7 @@ class MyAnalyticsController extends BaseController {
|
|||
final Map<String, List<PangeaMessageEvent>> langCodeToMsgs =
|
||||
await getLangCodesToMsgs(
|
||||
userL2,
|
||||
leastRecentUpdate,
|
||||
l2AnalyticsLastUpdated,
|
||||
);
|
||||
|
||||
final List<String> langCodes = langCodeToMsgs.keys.toList();
|
||||
|
|
@ -223,7 +237,7 @@ class MyAnalyticsController extends BaseController {
|
|||
// message in this language at the time of the last analytics update
|
||||
// so fallback to the least recent update time
|
||||
final DateTime? lastUpdated =
|
||||
lastUpdatedMap[analyticsRoom.id] ?? leastRecentUpdate;
|
||||
lastUpdatedMap[analyticsRoom.id] ?? l2AnalyticsLastUpdated;
|
||||
|
||||
// get the corresponding list of recent messages for this langCode
|
||||
final List<PangeaMessageEvent> recentMsgs =
|
||||
|
|
|
|||
43
lib/pangea/enum/assistance_state_enum.dart
Normal file
43
lib/pangea/enum/assistance_state_enum.dart
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// assistance state is, user has not typed a message, user has typed a message and IGC has not run,
|
||||
// IGC is running, IGC has run and there are remaining steps (either IT or IGC), or all steps are done
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/constants/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
enum AssistanceState {
|
||||
noMessage,
|
||||
notFetched,
|
||||
fetching,
|
||||
fetched,
|
||||
complete,
|
||||
}
|
||||
|
||||
extension AssistanceStateExtension on AssistanceState {
|
||||
Color stateColor(context) {
|
||||
switch (this) {
|
||||
case AssistanceState.noMessage:
|
||||
case AssistanceState.notFetched:
|
||||
case AssistanceState.fetching:
|
||||
return Theme.of(context).colorScheme.primary;
|
||||
case AssistanceState.fetched:
|
||||
return PangeaColors.igcError;
|
||||
case AssistanceState.complete:
|
||||
return AppConfig.success;
|
||||
}
|
||||
}
|
||||
|
||||
String tooltip(L10n l10n) {
|
||||
switch (this) {
|
||||
case AssistanceState.noMessage:
|
||||
case AssistanceState.notFetched:
|
||||
return l10n.runGrammarCorrection;
|
||||
case AssistanceState.fetching:
|
||||
return "";
|
||||
case AssistanceState.fetched:
|
||||
return l10n.grammarCorrectionFailed;
|
||||
case AssistanceState.complete:
|
||||
return l10n.grammarCorrectionComplete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,14 +4,17 @@ import 'dart:developer';
|
|||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fluffychat/pangea/constants/class_default_values.dart';
|
||||
import 'package:fluffychat/pangea/constants/language_keys.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/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';
|
||||
import 'package:fluffychat/pangea/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/bot_name.dart';
|
||||
|
|
@ -129,6 +132,9 @@ extension PangeaRoom on Room {
|
|||
|
||||
Event? get pangeaRoomRulesStateEvent => _pangeaRoomRulesStateEvent;
|
||||
|
||||
Future<List<LanguageModel>> targetLanguages() async =>
|
||||
await _targetLanguages();
|
||||
|
||||
// events
|
||||
|
||||
Future<bool> leaveIfFull() async => await _leaveIfFull();
|
||||
|
|
|
|||
|
|
@ -92,6 +92,34 @@ extension SpaceRoomExtension on Room {
|
|||
return null;
|
||||
}
|
||||
|
||||
Future<List<LanguageModel>> _targetLanguages() async {
|
||||
await requestParticipants();
|
||||
final students = _students;
|
||||
|
||||
final Map<LanguageModel, int> langCounts = {};
|
||||
final List<Room> allRooms = client.rooms;
|
||||
for (final User student in students) {
|
||||
for (final Room room in allRooms) {
|
||||
if (!room.isAnalyticsRoomOfUser(student.id)) continue;
|
||||
final String? langCode = room.madeForLang;
|
||||
if (langCode == null ||
|
||||
langCode.isEmpty ||
|
||||
langCode == LanguageKeys.unknownLanguage) {
|
||||
continue;
|
||||
}
|
||||
final LanguageModel lang = PangeaLanguage.byLangCode(langCode);
|
||||
langCounts[lang] ??= 0;
|
||||
langCounts[lang] = langCounts[lang]! + 1;
|
||||
}
|
||||
}
|
||||
// get a list of language models, sorted
|
||||
// by the number of students who are learning that language
|
||||
return langCounts.entries.map((entry) => entry.key).toList()
|
||||
..sort(
|
||||
(a, b) => langCounts[b]!.compareTo(langCounts[a]!),
|
||||
);
|
||||
}
|
||||
|
||||
// DateTime? get _languageSettingsUpdatedAt {
|
||||
// if (!isSpace) return null;
|
||||
// return languageSettingsStateEvent?.originServerTs ?? creationTime;
|
||||
|
|
|
|||
|
|
@ -25,8 +25,9 @@ class BaseAnalyticsPage extends StatefulWidget {
|
|||
final AnalyticsSelected defaultSelected;
|
||||
final AnalyticsSelected? alwaysSelected;
|
||||
final StudentAnalyticsController? myAnalyticsController;
|
||||
final List<LanguageModel> targetLanguages;
|
||||
|
||||
const BaseAnalyticsPage({
|
||||
BaseAnalyticsPage({
|
||||
super.key,
|
||||
required this.pageTitle,
|
||||
required this.tabs,
|
||||
|
|
@ -34,7 +35,10 @@ class BaseAnalyticsPage extends StatefulWidget {
|
|||
required this.defaultSelected,
|
||||
this.selectedView,
|
||||
this.myAnalyticsController,
|
||||
});
|
||||
targetLanguages,
|
||||
}) : targetLanguages = (targetLanguages?.isNotEmpty ?? false)
|
||||
? targetLanguages
|
||||
: MatrixState.pangeaController.pLanguageStore.targetOptions;
|
||||
|
||||
@override
|
||||
State<BaseAnalyticsPage> createState() => BaseAnalyticsController();
|
||||
|
|
|
|||
|
|
@ -104,208 +104,220 @@ class BaseAnalyticsView extends StatelessWidget {
|
|||
),
|
||||
body: MaxWidthBody(
|
||||
withScrolling: false,
|
||||
child: Column(
|
||||
children: [
|
||||
if (controller.widget.selectedView != null)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
child: controller.widget.selectedView != null
|
||||
? Column(
|
||||
children: [
|
||||
TimeSpanMenuButton(
|
||||
value: controller.currentTimeSpan,
|
||||
onChange: (TimeSpan value) =>
|
||||
controller.toggleTimeSpan(context, value),
|
||||
),
|
||||
AnalyticsLanguageButton(
|
||||
value: controller
|
||||
.pangeaController.analytics.currentAnalyticsLang,
|
||||
onChange: (lang) => controller.toggleSpaceLang(lang),
|
||||
languages: controller
|
||||
.pangeaController.pLanguageStore.targetOptions,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (controller.widget.selectedView != null)
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: chartView(context),
|
||||
),
|
||||
if (controller.widget.selectedView != null)
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: DefaultTabController(
|
||||
length: 2,
|
||||
child: Column(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
TabBar(
|
||||
tabs: [
|
||||
...controller.widget.tabs.map(
|
||||
(tab) => Tab(
|
||||
icon: Icon(
|
||||
tab.icon,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
// if (controller.widget.defaultSelected.type ==
|
||||
// AnalyticsEntryType.student)
|
||||
// IconButton(
|
||||
// icon: const Icon(Icons.refresh),
|
||||
// onPressed: controller.onRefresh,
|
||||
// tooltip: L10n.of(context)!.refresh,
|
||||
// ),
|
||||
TimeSpanMenuButton(
|
||||
value: controller.currentTimeSpan,
|
||||
onChange: (TimeSpan value) =>
|
||||
controller.toggleTimeSpan(context, value),
|
||||
),
|
||||
AnalyticsLanguageButton(
|
||||
value: controller
|
||||
.pangeaController.analytics.currentAnalyticsLang,
|
||||
onChange: (lang) => controller.toggleSpaceLang(lang),
|
||||
languages: controller.widget.targetLanguages,
|
||||
),
|
||||
],
|
||||
),
|
||||
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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: max(
|
||||
controller.widget.tabs[0].items.length + 1,
|
||||
controller.widget.tabs[1].items.length,
|
||||
) *
|
||||
73,
|
||||
),
|
||||
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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
const Divider(height: 1),
|
||||
ListTile(
|
||||
title: Text(L10n.of(context)!.grammarAnalytics),
|
||||
leading: CircleAvatar(
|
||||
backgroundColor:
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
foregroundColor:
|
||||
Theme.of(context).textTheme.bodyLarge!.color,
|
||||
child: Icon(BarChartViewSelection.grammar.icon),
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
String route =
|
||||
"/rooms/${controller.widget.defaultSelected.type.route}";
|
||||
if (controller.widget.defaultSelected.type ==
|
||||
AnalyticsEntryType.space) {
|
||||
route += "/${controller.widget.defaultSelected.id}";
|
||||
}
|
||||
route += "/${BarChartViewSelection.grammar.route}";
|
||||
context.go(route);
|
||||
},
|
||||
),
|
||||
const Divider(height: 1),
|
||||
ListTile(
|
||||
title: Text(L10n.of(context)!.messageAnalytics),
|
||||
leading: CircleAvatar(
|
||||
backgroundColor:
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
foregroundColor:
|
||||
Theme.of(context).textTheme.bodyLarge!.color,
|
||||
child: Icon(BarChartViewSelection.messages.icon),
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
String route =
|
||||
"/rooms/${controller.widget.defaultSelected.type.route}";
|
||||
if (controller.widget.defaultSelected.type ==
|
||||
AnalyticsEntryType.space) {
|
||||
route += "/${controller.widget.defaultSelected.id}";
|
||||
}
|
||||
route += "/${BarChartViewSelection.messages.route}";
|
||||
context.go(route);
|
||||
},
|
||||
),
|
||||
const Divider(height: 1),
|
||||
],
|
||||
),
|
||||
if (controller.widget.selectedView == null)
|
||||
const Divider(height: 1),
|
||||
if (controller.widget.selectedView == null)
|
||||
ListTile(
|
||||
title: Text(L10n.of(context)!.grammarAnalytics),
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
foregroundColor: Theme.of(context).textTheme.bodyLarge!.color,
|
||||
child: Icon(BarChartViewSelection.grammar.icon),
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
String route =
|
||||
"/rooms/${controller.widget.defaultSelected.type.route}";
|
||||
if (controller.widget.defaultSelected.type ==
|
||||
AnalyticsEntryType.space) {
|
||||
route += "/${controller.widget.defaultSelected.id}";
|
||||
}
|
||||
route += "/${BarChartViewSelection.grammar.route}";
|
||||
context.go(route);
|
||||
},
|
||||
),
|
||||
if (controller.widget.selectedView == null)
|
||||
const Divider(height: 1),
|
||||
if (controller.widget.selectedView == null)
|
||||
ListTile(
|
||||
title: Text(L10n.of(context)!.messageAnalytics),
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
foregroundColor: Theme.of(context).textTheme.bodyLarge!.color,
|
||||
child: Icon(BarChartViewSelection.messages.icon),
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
String route =
|
||||
"/rooms/${controller.widget.defaultSelected.type.route}";
|
||||
if (controller.widget.defaultSelected.type ==
|
||||
AnalyticsEntryType.space) {
|
||||
route += "/${controller.widget.defaultSelected.id}";
|
||||
}
|
||||
route += "/${BarChartViewSelection.messages.route}";
|
||||
context.go(route);
|
||||
},
|
||||
),
|
||||
if (controller.widget.selectedView == null)
|
||||
const Divider(height: 1),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ 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';
|
||||
|
|
@ -33,6 +34,18 @@ class SpaceAnalyticsV2Controller extends State<SpaceAnalyticsPage> {
|
|||
List<User> students = [];
|
||||
String? get spaceId => GoRouterState.of(context).pathParameters['spaceid'];
|
||||
Room? _spaceRoom;
|
||||
List<LanguageModel> targetLanguages = [];
|
||||
|
||||
@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) {
|
||||
|
|
@ -44,23 +57,11 @@ class SpaceAnalyticsV2Controller extends State<SpaceAnalyticsPage> {
|
|||
context.go('/rooms/analytics');
|
||||
return null;
|
||||
}
|
||||
getChatAndStudents();
|
||||
getChatAndStudents().then((_) => setTargetLanguages());
|
||||
}
|
||||
return _spaceRoom;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
debugPrint("init space analytics");
|
||||
Future.delayed(Duration.zero, () async {
|
||||
if (spaceRoom == null || (!(spaceRoom?.isSpace ?? false))) {
|
||||
context.go('/rooms');
|
||||
}
|
||||
getChatAndStudents();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> getChatAndStudents() async {
|
||||
try {
|
||||
await spaceRoom?.postLoad();
|
||||
|
|
@ -97,12 +98,12 @@ class SpaceAnalyticsV2Controller extends State<SpaceAnalyticsPage> {
|
|||
}
|
||||
}
|
||||
|
||||
// @override
|
||||
// void dispose() {
|
||||
// super.dispose();
|
||||
// refreshTimer?.cancel();
|
||||
// stateSub?.cancel();
|
||||
// }
|
||||
Future<void> 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) {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ class SpaceAnalyticsView extends StatelessWidget {
|
|||
AnalyticsEntryType.space,
|
||||
controller.spaceRoom?.name ?? "",
|
||||
),
|
||||
targetLanguages: controller.targetLanguages,
|
||||
)
|
||||
: const SizedBox();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ 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';
|
||||
|
|
@ -23,22 +24,12 @@ class AnalyticsSpaceListController extends State<AnalyticsSpaceList> {
|
|||
PangeaController pangeaController = MatrixState.pangeaController;
|
||||
List<Room> spaces = [];
|
||||
StreamSubscription? stateSub;
|
||||
List<LanguageModel> targetLanguages = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Matrix.of(context).client.spacesImTeaching.then((spaceList) {
|
||||
spaceList = spaceList
|
||||
.where(
|
||||
(space) => !spaceList.any(
|
||||
(parentSpace) => parentSpace.spaceChildren
|
||||
.any((child) => child.roomId == space.id),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
spaces = spaceList;
|
||||
setState(() {});
|
||||
});
|
||||
setSpaceList().then((_) => setTargetLanguages());
|
||||
|
||||
// reload dropdowns when their values change in analytics page
|
||||
stateSub = pangeaController.analytics.stateStream.listen(
|
||||
|
|
@ -54,6 +45,36 @@ class AnalyticsSpaceListController extends State<AnalyticsSpaceList> {
|
|||
|
||||
StreamController refreshStream = StreamController.broadcast();
|
||||
|
||||
Future<void> 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<void> setTargetLanguages() async {
|
||||
if (spaces.isEmpty) return;
|
||||
final Map<LanguageModel, int> langCounts = {};
|
||||
for (final Room space in spaces) {
|
||||
final List<LanguageModel> 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);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import 'package:sentry_flutter/sentry_flutter.dart';
|
|||
|
||||
class PangeaAnyState {
|
||||
final Map<String, LayerLinkAndKey> _layerLinkAndKeys = {};
|
||||
OverlayEntry? overlay;
|
||||
List<OverlayEntry> entries = [];
|
||||
|
||||
dispose() {
|
||||
closeOverlay();
|
||||
|
|
@ -32,26 +32,32 @@ class PangeaAnyState {
|
|||
_layerLinkAndKeys.remove(transformTargetId);
|
||||
}
|
||||
|
||||
void openOverlay(OverlayEntry entry, BuildContext context) {
|
||||
closeOverlay();
|
||||
overlay = entry;
|
||||
Overlay.of(context).insert(overlay!);
|
||||
void openOverlay(
|
||||
OverlayEntry entry,
|
||||
BuildContext context, {
|
||||
bool closePrevOverlay = true,
|
||||
}) {
|
||||
if (closePrevOverlay) {
|
||||
closeOverlay();
|
||||
}
|
||||
entries.add(entry);
|
||||
Overlay.of(context).insert(entry);
|
||||
}
|
||||
|
||||
void closeOverlay() {
|
||||
if (overlay != null) {
|
||||
if (entries.isNotEmpty) {
|
||||
try {
|
||||
overlay?.remove();
|
||||
entries.last.remove();
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
s: s,
|
||||
data: {
|
||||
"overlay": overlay,
|
||||
"overlay": entries.last,
|
||||
},
|
||||
);
|
||||
}
|
||||
overlay = null;
|
||||
entries.removeLast();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ class InstructionsController {
|
|||
),
|
||||
cardSize: const Size(300.0, 300.0),
|
||||
transformTargetId: transformTargetKey,
|
||||
closePrevOverlay: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,9 +25,12 @@ class OverlayUtil {
|
|||
Color? backgroundColor,
|
||||
Alignment? targetAnchor,
|
||||
Alignment? followerAnchor,
|
||||
bool closePrevOverlay = true,
|
||||
}) {
|
||||
try {
|
||||
MatrixState.pAnyState.closeOverlay();
|
||||
if (closePrevOverlay) {
|
||||
MatrixState.pAnyState.closeOverlay();
|
||||
}
|
||||
final LayerLinkAndKey layerLinkAndKey =
|
||||
MatrixState.pAnyState.layerLinkAndKey(transformTargetId);
|
||||
|
||||
|
|
@ -58,7 +61,8 @@ class OverlayUtil {
|
|||
),
|
||||
);
|
||||
|
||||
MatrixState.pAnyState.openOverlay(entry, context);
|
||||
MatrixState.pAnyState
|
||||
.openOverlay(entry, context, closePrevOverlay: closePrevOverlay);
|
||||
} catch (err, stack) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(e: err, s: stack);
|
||||
|
|
@ -72,6 +76,7 @@ class OverlayUtil {
|
|||
required String transformTargetId,
|
||||
backDropToDismiss = true,
|
||||
Color? borderColor,
|
||||
bool closePrevOverlay = true,
|
||||
}) {
|
||||
try {
|
||||
final LayerLinkAndKey layerLinkAndKey =
|
||||
|
|
@ -105,6 +110,7 @@ class OverlayUtil {
|
|||
offset: cardOffset,
|
||||
backDropToDismiss: backDropToDismiss,
|
||||
borderColor: borderColor,
|
||||
closePrevOverlay: closePrevOverlay,
|
||||
);
|
||||
} catch (err, stack) {
|
||||
debugger(when: kDebugMode);
|
||||
|
|
@ -180,7 +186,7 @@ class OverlayUtil {
|
|||
return Offset(dx, dy);
|
||||
}
|
||||
|
||||
static bool get isOverlayOpen => MatrixState.pAnyState.overlay != null;
|
||||
static bool get isOverlayOpen => MatrixState.pAnyState.entries.isNotEmpty;
|
||||
}
|
||||
|
||||
class TransparentBackdrop extends StatelessWidget {
|
||||
|
|
|
|||
|
|
@ -136,8 +136,8 @@ class ToolbarDisplayController {
|
|||
backgroundColor: const Color.fromRGBO(0, 0, 0, 1).withAlpha(100),
|
||||
);
|
||||
|
||||
if (MatrixState.pAnyState.overlay != null) {
|
||||
overlayId = MatrixState.pAnyState.overlay.hashCode.toString();
|
||||
if (MatrixState.pAnyState.entries.isNotEmpty) {
|
||||
overlayId = MatrixState.pAnyState.entries.last.hashCode.toString();
|
||||
}
|
||||
|
||||
if (mode != null) {
|
||||
|
|
@ -151,8 +151,11 @@ class ToolbarDisplayController {
|
|||
|
||||
bool get highlighted {
|
||||
if (overlayId == null) return false;
|
||||
if (MatrixState.pAnyState.overlay == null) overlayId = null;
|
||||
return MatrixState.pAnyState.overlay.hashCode.toString() == overlayId;
|
||||
if (MatrixState.pAnyState.entries.isEmpty) {
|
||||
overlayId = null;
|
||||
return false;
|
||||
}
|
||||
return MatrixState.pAnyState.entries.last.hashCode.toString() == overlayId;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ class SubscriptionCard extends StatelessWidget {
|
|||
title ?? subscription?.displayName(context) ?? '',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontSize: 20,
|
||||
color:
|
||||
enabled ? null : const Color.fromARGB(255, 174, 174, 174),
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue