Merge branch '375-introduce-bot-custom-mode' of https://github.com/pangeachat/client into 375-introduce-bot-custom-mode
This commit is contained in:
commit
7d9b2f7b3e
20 changed files with 890 additions and 715 deletions
|
|
@ -4062,5 +4062,12 @@
|
|||
"suggestToSpaceDesc": "Suggested spaces will appear in the chat lists for their parent spaces",
|
||||
"practice": "Practice",
|
||||
"noLanguagesSet": "No languages set",
|
||||
"noActivitiesFound": "No practice activities found for this message"
|
||||
"noActivitiesFound": "No practice activities found for this message",
|
||||
"languageButtonLabel": "Language: {currentLanguage}",
|
||||
"@languageButtonLabel": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"currentLanguage": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -62,12 +62,13 @@ class AnalyticsController extends BaseController {
|
|||
timeSpan.toString(),
|
||||
local: true,
|
||||
);
|
||||
setState();
|
||||
}
|
||||
|
||||
///////// SPACE ANALYTICS LANGUAGES //////////
|
||||
String get _analyticsSpaceLangKey => "ANALYTICS_SPACE_LANG_KEY";
|
||||
|
||||
LanguageModel get currentAnalyticsSpaceLang {
|
||||
LanguageModel get currentAnalyticsLang {
|
||||
try {
|
||||
final String? str = _pangeaController.pStoreService.read(
|
||||
_analyticsSpaceLangKey,
|
||||
|
|
@ -83,41 +84,43 @@ class AnalyticsController extends BaseController {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> setCurrentAnalyticsSpaceLang(LanguageModel lang) async {
|
||||
Future<void> setCurrentAnalyticsLang(LanguageModel lang) async {
|
||||
await _pangeaController.pStoreService.save(
|
||||
_analyticsSpaceLangKey,
|
||||
lang.langCode,
|
||||
local: true,
|
||||
);
|
||||
setState();
|
||||
}
|
||||
|
||||
/// given an analytics event type and the current analytics language,
|
||||
/// get the last time the user updated their analytics
|
||||
Future<DateTime?> myAnalyticsLastUpdated(String type) async {
|
||||
// given an analytics event type, get the last updated times
|
||||
// for each of the user's analytics rooms and return the most recent
|
||||
// Most Recent instead of the oldest because, for instance:
|
||||
// My last Spanish event was sent 3 days ago.
|
||||
// My last English event was sent 1 day ago.
|
||||
// When I go to check if the cached data is out of date, the cached item was set 2 days ago.
|
||||
// I know there’s new data available because the English update data (the most recent) is after the cache’s creation time.
|
||||
// So, I should update the cache.
|
||||
final List<Room> analyticsRooms = _pangeaController
|
||||
.matrixState.client.allMyAnalyticsRooms
|
||||
.where((room) => room.isAnalyticsRoom)
|
||||
.toList();
|
||||
|
||||
final List<DateTime> lastUpdates = [];
|
||||
final Map<String, DateTime> 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) {
|
||||
lastUpdates.add(lastUpdated);
|
||||
langCodeLastUpdates[roomLang] = lastUpdated;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastUpdates.isEmpty) return null;
|
||||
return lastUpdates.reduce(
|
||||
if (langCodeLastUpdates.isEmpty) return null;
|
||||
final String? l2Code =
|
||||
_pangeaController.languageController.userL2?.langCode;
|
||||
if (l2Code != null && langCodeLastUpdates.containsKey(l2Code)) {
|
||||
return langCodeLastUpdates[l2Code];
|
||||
}
|
||||
return langCodeLastUpdates.values.reduce(
|
||||
(check, mostRecent) => check.isAfter(mostRecent) ? check : mostRecent,
|
||||
);
|
||||
}
|
||||
|
|
@ -134,7 +137,7 @@ class AnalyticsController extends BaseController {
|
|||
final List<Future<DateTime?>> lastUpdatedFutures = [];
|
||||
for (final student in space.students) {
|
||||
final Room? analyticsRoom = _pangeaController.matrixState.client
|
||||
.analyticsRoomLocal(currentAnalyticsSpaceLang.langCode, student.id);
|
||||
.analyticsRoomLocal(currentAnalyticsLang.langCode, student.id);
|
||||
if (analyticsRoom == null) continue;
|
||||
lastUpdatedFutures.add(
|
||||
analyticsRoom.analyticsLastUpdated(
|
||||
|
|
@ -177,28 +180,20 @@ class AnalyticsController extends BaseController {
|
|||
|
||||
//////////////////////////// MESSAGE SUMMARY ANALYTICS ////////////////////////////
|
||||
|
||||
/// get all the summary analytics events for the current user
|
||||
/// in the current language's analytics room
|
||||
Future<List<SummaryAnalyticsEvent>> mySummaryAnalytics() async {
|
||||
// gets all the summary analytics events for the user
|
||||
// since the current timespace's cut off date
|
||||
final analyticsRooms =
|
||||
_pangeaController.matrixState.client.allMyAnalyticsRooms;
|
||||
final Room? analyticsRoom = _pangeaController.matrixState.client
|
||||
.analyticsRoomLocal(currentAnalyticsLang.langCode);
|
||||
if (analyticsRoom == null) return [];
|
||||
|
||||
final List<SummaryAnalyticsEvent> allEvents = [];
|
||||
|
||||
// TODO switch to using list of futures
|
||||
for (final Room analyticsRoom in analyticsRooms) {
|
||||
final List<AnalyticsEvent>? roomEvents =
|
||||
await analyticsRoom.getAnalyticsEvents(
|
||||
type: PangeaEventTypes.summaryAnalytics,
|
||||
since: currentAnalyticsTimeSpan.cutOffDate,
|
||||
userId: _pangeaController.matrixState.client.userID!,
|
||||
);
|
||||
|
||||
allEvents.addAll(
|
||||
roomEvents?.cast<SummaryAnalyticsEvent>() ?? [],
|
||||
);
|
||||
}
|
||||
return allEvents;
|
||||
final List<AnalyticsEvent>? roomEvents =
|
||||
await analyticsRoom.getAnalyticsEvents(
|
||||
type: PangeaEventTypes.summaryAnalytics,
|
||||
since: currentAnalyticsTimeSpan.cutOffDate,
|
||||
userId: _pangeaController.matrixState.client.userID!,
|
||||
);
|
||||
return roomEvents?.cast<SummaryAnalyticsEvent>() ?? [];
|
||||
}
|
||||
|
||||
Future<List<SummaryAnalyticsEvent>> spaceMemberAnalytics(
|
||||
|
|
@ -216,7 +211,7 @@ class AnalyticsController extends BaseController {
|
|||
final List<SummaryAnalyticsEvent> analyticsEvents = [];
|
||||
for (final student in space.students) {
|
||||
final Room? analyticsRoom = _pangeaController.matrixState.client
|
||||
.analyticsRoomLocal(currentAnalyticsSpaceLang.langCode, student.id);
|
||||
.analyticsRoomLocal(currentAnalyticsLang.langCode, student.id);
|
||||
|
||||
if (analyticsRoom != null) {
|
||||
final List<AnalyticsEvent>? roomEvents =
|
||||
|
|
@ -261,7 +256,7 @@ class AnalyticsController extends BaseController {
|
|||
(e.defaultSelected.type == defaultSelected.type) &&
|
||||
(e.selected?.id == selected?.id) &&
|
||||
(e.selected?.type == selected?.type) &&
|
||||
(e.langCode == currentAnalyticsSpaceLang.langCode),
|
||||
(e.langCode == currentAnalyticsLang.langCode),
|
||||
);
|
||||
|
||||
if (index != -1) {
|
||||
|
|
@ -289,7 +284,7 @@ class AnalyticsController extends BaseController {
|
|||
chartAnalyticsModel: chartAnalyticsModel,
|
||||
defaultSelected: defaultSelected,
|
||||
selected: selected,
|
||||
langCode: currentAnalyticsSpaceLang.langCode,
|
||||
langCode: currentAnalyticsLang.langCode,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -525,20 +520,18 @@ class AnalyticsController extends BaseController {
|
|||
//////////////////////////// CONSTRUCTS ////////////////////////////
|
||||
|
||||
Future<List<ConstructAnalyticsEvent>> allMyConstructs() async {
|
||||
final List<Room> analyticsRooms =
|
||||
_pangeaController.matrixState.client.allMyAnalyticsRooms;
|
||||
final Room? analyticsRoom = _pangeaController.matrixState.client
|
||||
.analyticsRoomLocal(currentAnalyticsLang.langCode);
|
||||
if (analyticsRoom == null) return [];
|
||||
|
||||
final List<ConstructAnalyticsEvent> allConstructs = [];
|
||||
for (final Room analyticsRoom in analyticsRooms) {
|
||||
final List<ConstructAnalyticsEvent>? roomEvents =
|
||||
(await analyticsRoom.getAnalyticsEvents(
|
||||
type: PangeaEventTypes.construct,
|
||||
since: currentAnalyticsTimeSpan.cutOffDate,
|
||||
userId: _pangeaController.matrixState.client.userID!,
|
||||
))
|
||||
?.cast<ConstructAnalyticsEvent>();
|
||||
allConstructs.addAll(roomEvents ?? []);
|
||||
}
|
||||
final List<ConstructAnalyticsEvent>? roomEvents =
|
||||
(await analyticsRoom.getAnalyticsEvents(
|
||||
type: PangeaEventTypes.construct,
|
||||
since: currentAnalyticsTimeSpan.cutOffDate,
|
||||
userId: _pangeaController.matrixState.client.userID!,
|
||||
))
|
||||
?.cast<ConstructAnalyticsEvent>();
|
||||
final List<ConstructAnalyticsEvent> allConstructs = roomEvents ?? [];
|
||||
|
||||
final List<String> adminSpaceRooms =
|
||||
await _pangeaController.matrixState.client.teacherRoomIds;
|
||||
|
|
@ -561,7 +554,7 @@ class AnalyticsController extends BaseController {
|
|||
final List<ConstructAnalyticsEvent> constructEvents = [];
|
||||
for (final student in space.students) {
|
||||
final Room? analyticsRoom = _pangeaController.matrixState.client
|
||||
.analyticsRoomLocal(currentAnalyticsSpaceLang.langCode, student.id);
|
||||
.analyticsRoomLocal(currentAnalyticsLang.langCode, student.id);
|
||||
if (analyticsRoom != null) {
|
||||
final List<ConstructAnalyticsEvent>? roomEvents =
|
||||
(await analyticsRoom.getAnalyticsEvents(
|
||||
|
|
@ -661,7 +654,7 @@ class AnalyticsController extends BaseController {
|
|||
e.defaultSelected.type == defaultSelected.type &&
|
||||
e.selected?.id == selected?.id &&
|
||||
e.selected?.type == selected?.type &&
|
||||
e.langCode == currentAnalyticsSpaceLang.langCode,
|
||||
e.langCode == currentAnalyticsLang.langCode,
|
||||
);
|
||||
|
||||
if (index > -1) {
|
||||
|
|
@ -687,7 +680,7 @@ class AnalyticsController extends BaseController {
|
|||
events: List.from(events),
|
||||
defaultSelected: defaultSelected,
|
||||
selected: selected,
|
||||
langCode: currentAnalyticsSpaceLang.langCode,
|
||||
langCode: currentAnalyticsLang.langCode,
|
||||
);
|
||||
_cachedConstructs.add(entry);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ class AnalyticsLanguageButton extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopupMenuButton<LanguageModel>(
|
||||
icon: const Icon(Icons.language_outlined),
|
||||
tooltip: L10n.of(context)!.changeAnalyticsLanguage,
|
||||
initialValue: value,
|
||||
onSelected: (LanguageModel? lang) {
|
||||
|
|
@ -33,6 +32,21 @@ class AnalyticsLanguageButton extends StatelessWidget {
|
|||
child: Text(lang.getDisplayName(context) ?? lang.langCode),
|
||||
);
|
||||
}).toList(),
|
||||
child: TextButton.icon(
|
||||
label: Text(
|
||||
L10n.of(context)!.languageButtonLabel(
|
||||
value.getDisplayName(context) ?? value.langCode,
|
||||
),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
icon: Icon(
|
||||
Icons.language_outlined,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
onPressed: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ class BaseAnalyticsController extends State<BaseAnalyticsPage> {
|
|||
}
|
||||
|
||||
Future<void> toggleSpaceLang(LanguageModel lang) async {
|
||||
await pangeaController.analytics.setCurrentAnalyticsSpaceLang(lang);
|
||||
await pangeaController.analytics.setCurrentAnalyticsLang(lang);
|
||||
await setChartData();
|
||||
refreshStream.add(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,28 +108,26 @@ class BaseAnalyticsView extends StatelessWidget {
|
|||
? Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
if (controller.widget.defaultSelected.type ==
|
||||
AnalyticsEntryType.student)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: controller.onRefresh,
|
||||
tooltip: L10n.of(context)!.refresh,
|
||||
),
|
||||
// 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),
|
||||
),
|
||||
if (controller.widget.defaultSelected.type ==
|
||||
AnalyticsEntryType.space)
|
||||
AnalyticsLanguageButton(
|
||||
value: controller.pangeaController.analytics
|
||||
.currentAnalyticsSpaceLang,
|
||||
onChange: (lang) => controller.toggleSpaceLang(lang),
|
||||
languages: controller.widget.targetLanguages,
|
||||
),
|
||||
AnalyticsLanguageButton(
|
||||
value: controller
|
||||
.pangeaController.analytics.currentAnalyticsLang,
|
||||
onChange: (lang) => controller.toggleSpaceLang(lang),
|
||||
languages: controller.widget.targetLanguages,
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
|
|
|
|||
|
|
@ -355,15 +355,17 @@ class ConstructMessagesDialog extends StatelessWidget {
|
|||
|
||||
final msgEventMatches = controller.getMessageEventMatches();
|
||||
|
||||
final noData = controller.constructs![controller.lemmaIndex].uses.length >
|
||||
controller._msgEvents.length;
|
||||
|
||||
return AlertDialog(
|
||||
title: Center(child: Text(controller.widget.controller.currentLemma!)),
|
||||
content: SizedBox(
|
||||
height: 350,
|
||||
width: 500,
|
||||
height: noData ? 90 : 250,
|
||||
width: noData ? 200 : 400,
|
||||
child: Column(
|
||||
children: [
|
||||
if (controller.constructs![controller.lemmaIndex].uses.length >
|
||||
controller._msgEvents.length)
|
||||
if (noData)
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
|
|
@ -398,8 +400,8 @@ class ConstructMessagesDialog extends StatelessWidget {
|
|||
child: Text(
|
||||
L10n.of(context)!.close.toUpperCase(),
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).textTheme.bodyMedium?.color?.withAlpha(150),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -23,13 +23,24 @@ class AnalyticsSpaceList extends StatefulWidget {
|
|||
class AnalyticsSpaceListController extends State<AnalyticsSpaceList> {
|
||||
PangeaController pangeaController = MatrixState.pangeaController;
|
||||
List<Room> spaces = [];
|
||||
StreamSubscription? stateSub;
|
||||
List<LanguageModel> targetLanguages = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
setSpaceList().then((_) => setTargetLanguages());
|
||||
|
||||
// reload dropdowns when their values change in analytics page
|
||||
stateSub = pangeaController.analytics.stateStream.listen(
|
||||
(_) => setState(() {}),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
stateSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
StreamController refreshStream = StreamController.broadcast();
|
||||
|
|
@ -71,7 +82,7 @@ class AnalyticsSpaceListController extends State<AnalyticsSpaceList> {
|
|||
}
|
||||
|
||||
Future<void> toggleSpaceLang(LanguageModel lang) async {
|
||||
await pangeaController.analytics.setCurrentAnalyticsSpaceLang(lang);
|
||||
await pangeaController.analytics.setCurrentAnalyticsLang(lang);
|
||||
refreshStream.add(false);
|
||||
setState(() {});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
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';
|
||||
|
|
@ -5,7 +6,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../enum/time_span.dart';
|
||||
import '../base_analytics.dart';
|
||||
import 'space_list.dart';
|
||||
|
||||
|
|
@ -32,27 +32,29 @@ class AnalyticsSpaceListView extends StatelessWidget {
|
|||
icon: const Icon(Icons.close_outlined),
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
actions: [
|
||||
TimeSpanMenuButton(
|
||||
value:
|
||||
controller.pangeaController.analytics.currentAnalyticsTimeSpan,
|
||||
onChange: (TimeSpan value) => controller.toggleTimeSpan(
|
||||
context,
|
||||
value,
|
||||
),
|
||||
),
|
||||
AnalyticsLanguageButton(
|
||||
value:
|
||||
controller.pangeaController.analytics.currentAnalyticsSpaceLang,
|
||||
onChange: (lang) => controller.toggleSpaceLang(lang),
|
||||
languages: controller.targetLanguages.isEmpty
|
||||
? controller.pangeaController.pLanguageStore.targetOptions
|
||||
: controller.targetLanguages,
|
||||
),
|
||||
],
|
||||
),
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/constants/language_keys.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';
|
||||
|
|
@ -75,6 +80,24 @@ class StudentAnalyticsController extends State<StudentAnalyticsPage> {
|
|||
return id;
|
||||
}
|
||||
|
||||
List<LanguageModel> get targetLanguages {
|
||||
final LanguageModel? l2 =
|
||||
_pangeaController.languageController.activeL2Model();
|
||||
final List<LanguageModel> 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(
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ class StudentAnalyticsView extends StatelessWidget {
|
|||
AnalyticsEntryType.student,
|
||||
L10n.of(context)!.allChatsAndClasses,
|
||||
),
|
||||
targetLanguages: controller.targetLanguages,
|
||||
)
|
||||
: const SizedBox();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ class TimeSpanMenuButton extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopupMenuButton<TimeSpan>(
|
||||
icon: const Icon(Icons.calendar_month_outlined),
|
||||
tooltip: L10n.of(context)!.changeDateRange,
|
||||
initialValue: value,
|
||||
onSelected: (TimeSpan? timeSpan) {
|
||||
|
|
@ -32,6 +31,19 @@ class TimeSpanMenuButton extends StatelessWidget {
|
|||
child: Text(timeSpan.string(context)),
|
||||
);
|
||||
}).toList(),
|
||||
child: TextButton.icon(
|
||||
label: Text(
|
||||
value.string(context),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
icon: Icon(
|
||||
Icons.calendar_month_outlined,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
onPressed: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class ClassDescriptionButton extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final iconColor = Theme.of(context).textTheme.bodyLarge!.color;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
|
|
@ -26,14 +27,27 @@ class ClassDescriptionButton extends StatelessWidget {
|
|||
foregroundColor: iconColor,
|
||||
child: const Icon(Icons.topic_outlined),
|
||||
),
|
||||
subtitle: Text(
|
||||
room.topic.isEmpty
|
||||
? (room.isRoomAdmin
|
||||
? (room.isSpace
|
||||
? L10n.of(context)!.classDescriptionDesc
|
||||
: L10n.of(context)!.chatTopicDesc)
|
||||
: L10n.of(context)!.topicNotSet)
|
||||
: room.topic,
|
||||
subtitle: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 190,
|
||||
),
|
||||
child: Scrollbar(
|
||||
controller: scrollController,
|
||||
interactive: true,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
primary: false,
|
||||
child: Text(
|
||||
room.topic.isEmpty
|
||||
? (room.isRoomAdmin
|
||||
? (room.isSpace
|
||||
? L10n.of(context)!.classDescriptionDesc
|
||||
: L10n.of(context)!.chatTopicDesc)
|
||||
: L10n.of(context)!.topicNotSet)
|
||||
: room.topic,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
room.isSpace
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -867,7 +867,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"be": [
|
||||
|
|
@ -2371,7 +2372,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"bn": [
|
||||
|
|
@ -3871,7 +3873,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"bo": [
|
||||
|
|
@ -5375,7 +5378,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"ca": [
|
||||
|
|
@ -6281,7 +6285,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"cs": [
|
||||
|
|
@ -7269,7 +7274,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"de": [
|
||||
|
|
@ -8140,7 +8146,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"el": [
|
||||
|
|
@ -9595,7 +9602,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"eo": [
|
||||
|
|
@ -10748,7 +10756,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"es": [
|
||||
|
|
@ -10767,7 +10776,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"et": [
|
||||
|
|
@ -11638,7 +11648,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"eu": [
|
||||
|
|
@ -12511,7 +12522,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"fa": [
|
||||
|
|
@ -13521,7 +13533,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"fi": [
|
||||
|
|
@ -14495,7 +14508,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"fil": [
|
||||
|
|
@ -15825,7 +15839,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"fr": [
|
||||
|
|
@ -16834,7 +16849,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"ga": [
|
||||
|
|
@ -17972,7 +17988,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"gl": [
|
||||
|
|
@ -18843,7 +18860,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"he": [
|
||||
|
|
@ -20100,7 +20118,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"hi": [
|
||||
|
|
@ -21597,7 +21616,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"hr": [
|
||||
|
|
@ -22547,7 +22567,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"hu": [
|
||||
|
|
@ -23434,7 +23455,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"ia": [
|
||||
|
|
@ -24924,7 +24946,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"id": [
|
||||
|
|
@ -25801,7 +25824,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"ie": [
|
||||
|
|
@ -27062,7 +27086,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"it": [
|
||||
|
|
@ -27990,7 +28015,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"ja": [
|
||||
|
|
@ -29029,7 +29055,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"ka": [
|
||||
|
|
@ -30387,7 +30414,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"ko": [
|
||||
|
|
@ -31260,7 +31288,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"lt": [
|
||||
|
|
@ -32299,7 +32328,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"lv": [
|
||||
|
|
@ -33178,7 +33208,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"nb": [
|
||||
|
|
@ -34381,7 +34412,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"nl": [
|
||||
|
|
@ -35348,7 +35380,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"pl": [
|
||||
|
|
@ -36324,7 +36357,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"pt": [
|
||||
|
|
@ -37806,7 +37840,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"pt_BR": [
|
||||
|
|
@ -38683,7 +38718,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"pt_PT": [
|
||||
|
|
@ -39887,7 +39923,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"ro": [
|
||||
|
|
@ -40898,7 +40935,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"ru": [
|
||||
|
|
@ -41775,7 +41813,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"sk": [
|
||||
|
|
@ -43045,7 +43084,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"sl": [
|
||||
|
|
@ -44445,7 +44485,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"sr": [
|
||||
|
|
@ -45619,7 +45660,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"sv": [
|
||||
|
|
@ -46527,7 +46569,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"ta": [
|
||||
|
|
@ -48028,7 +48071,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"th": [
|
||||
|
|
@ -49483,7 +49527,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"tr": [
|
||||
|
|
@ -50354,7 +50399,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"uk": [
|
||||
|
|
@ -51262,7 +51308,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"vi": [
|
||||
|
|
@ -52618,7 +52665,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"zh": [
|
||||
|
|
@ -53489,7 +53537,8 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
],
|
||||
|
||||
"zh_Hant": [
|
||||
|
|
@ -54641,6 +54690,7 @@
|
|||
"suggestToSpaceDesc",
|
||||
"practice",
|
||||
"noLanguagesSet",
|
||||
"noActivitiesFound"
|
||||
"noActivitiesFound",
|
||||
"languageButtonLabel"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue