merge github-issues into auto-pr

pull/1077/head
Gabby Gurdin 2 years ago
commit 70e085e4f0

@ -3988,5 +3988,6 @@
"appname": {},
"unread": {}
}
}
},
"messageAnalytics": "Message Analytics"
}

@ -549,6 +549,12 @@ class ChatController extends State<ChatPageWithRoom>
});
// #Pangea
final List<String> edittingEvents = [];
void clearEdittingEvent(String eventId) {
edittingEvents.remove(eventId);
setState(() {});
}
// Future<void> send() async {
// Original send function gets the tx id within the matrix lib,
// but for choero, the tx id is generated before the message send.
@ -591,6 +597,7 @@ class ChatController extends State<ChatPageWithRoom>
// editEventId: editEvent?.eventId,
// parseCommands: parseCommands,
// );
final previousEdit = editEvent;
room
.pangeaSendTextEvent(
sendController.text,
@ -606,6 +613,13 @@ class ChatController extends State<ChatPageWithRoom>
)
.then(
(String? msgEventId) {
// #Pangea
setState(() {
if (previousEdit != null) {
edittingEvents.add(previousEdit.eventId);
}
});
// Pangea#
GoogleAnalytics.sendMessage(
room.id,
room.classCode,
@ -628,6 +642,7 @@ class ChatController extends State<ChatPageWithRoom>
useType: useType ?? UseType.un,
time: DateTime.now(),
),
isEdit: previousEdit != null,
);
if (choreo != null &&
@ -641,6 +656,7 @@ class ChatController extends State<ChatPageWithRoom>
...choreo.toGrammarConstructUse(msgEventId, room.id),
],
originalSent!.langCode,
isEdit: previousEdit != null,
);
}
},

@ -31,9 +31,9 @@ class AudioPlayerWidget extends StatefulWidget {
this.color = Colors.black,
// #Pangea
this.matrixFile,
super.key,
this.autoplay = false,
// Pangea#
super.key,
});
@override
@ -236,17 +236,6 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
late final List<int> waveform;
// #Pangea
Future<void> _downloadMatrixFile() async {
if (kIsWeb) return;
final temp = await getTemporaryDirectory();
final tempDir = temp;
final file = File('${tempDir.path}/${widget.matrixFile!.name}');
await file.writeAsBytes(widget.matrixFile!.bytes);
audioFile = file;
}
// Pangea#
void _toggleSpeed() async {
final audioPlayer = this.audioPlayer;
if (audioPlayer == null) return;
@ -268,6 +257,17 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
setState(() {});
}
// #Pangea
Future<void> _downloadMatrixFile() async {
if (kIsWeb) return;
final temp = await getTemporaryDirectory();
final tempDir = temp;
final file = File('${tempDir.path}/${widget.matrixFile!.name}');
await file.writeAsBytes(widget.matrixFile!.bytes);
audioFile = file;
}
// Pangea#
@override
void initState() {
super.initState();

@ -2,6 +2,7 @@ import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/enum/use_type.dart';
import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:fluffychat/pangea/models/pangea_message_event.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
import 'package:fluffychat/utils/date_time_extension.dart';
import 'package:fluffychat/utils/string_color.dart';
@ -71,10 +72,21 @@ class Message extends StatelessWidget {
super.key,
});
// #Pangea
PangeaMessageEvent? get pangeaMessageEvent =>
controller.getPangeaMessageEvent(event.eventId);
// Pangea#
@override
Widget build(BuildContext context) {
// #Pangea
debugPrint('Message.build()');
WidgetsBinding.instance.addPostFrameCallback((_) {
if (controller.edittingEvents.contains(event.eventId)) {
pangeaMessageEvent?.updateLatestEdit();
controller.clearEdittingEvent(event.eventId);
}
});
// Pangea#
if (!{
EventTypes.Message,

@ -27,10 +27,7 @@ class RecordingDialogState extends State<RecordingDialog> {
bool error = false;
String? _recordedPath;
// #Pangea
// final _audioRecorder = Record();
final _audioRecorder = AudioRecorder();
// Pangea#
final _audioRecorder = Record();
final List<double> amplitudeTimeline = [];
static const int bitRate = 64000;
@ -48,28 +45,11 @@ class RecordingDialogState extends State<RecordingDialog> {
return;
}
await WakelockPlus.enable();
// We try to pick Opus where supported, since that is a codec optimized
// for speech as well as what the voice messages MSC uses.
final audioCodec =
(await _audioRecorder.isEncoderSupported(AudioEncoder.opus))
? AudioEncoder.opus
: AudioEncoder.aacLc;
// #Pangea
// await _audioRecorder.start(
// path: _recordedPath,
// bitRate: bitRate,
// samplingRate: samplingRate,
// );
await _audioRecorder.start(
RecordConfig(
encoder: audioCodec,
bitRate: bitRate,
// samplingRate: samplingRate,
),
path: _recordedPath!,
path: _recordedPath,
bitRate: bitRate,
samplingRate: samplingRate,
);
// Pangea#
setState(() => _duration = Duration.zero);
_recorderSubscription?.cancel();
_recorderSubscription =

@ -818,6 +818,7 @@ class ChatListController extends State<ChatList>
await pangeaController.subscriptionController.initialize();
pangeaController.afterSyncAndFirstLoginInitialization(context);
await pangeaController.inviteBotToExistingSpaces();
await pangeaController.setPangeaPushRules();
} else {
ErrorHandler.logError(
m: "didn't run afterSyncAndFirstLoginInitialization because not mounted",

@ -18,4 +18,5 @@ class PangeaEventTypes {
static const botOptions = "pangea.bot_options";
static const userAge = "pangea.user_age";
static const textToSpeechRule = "p.rule.text_to_speech";
}

@ -18,8 +18,7 @@ class MessageDataController extends BaseController {
late PangeaController _pangeaController;
final List<CacheItem> _cache = [];
final Map<String, MessageDataQueueItem> _messageDataToSave = {};
final List<RepresentationCacheItem> _representationCache = [];
MessageDataController(PangeaController pangeaController) {
_pangeaController = pangeaController;
@ -31,6 +30,14 @@ class MessageDataController extends BaseController {
e.parentId == parentId && e.type == type && e.langCode == langCode,
);
RepresentationCacheItem? getRepresentationCacheItem(
String parentId,
String langCode,
) =>
_representationCache.firstWhereOrNull(
(e) => e.parentId == parentId && e.langCode == langCode,
);
Future<PangeaMessageTokens?> _getTokens(
TokensRequestModel req,
) async {
@ -141,6 +148,32 @@ class MessageDataController extends BaseController {
required String? source,
required String target,
required Room room,
}) async {
final RepresentationCacheItem? item =
getRepresentationCacheItem(text, target);
if (item != null) return item.data;
_representationCache.add(
RepresentationCacheItem(
text,
target,
_getPangeaRepresentation(
text: text,
source: source,
target: target,
room: room,
),
),
);
return _representationCache.last.data;
}
Future<PangeaRepresentation?> _getPangeaRepresentation({
required String text,
required String? source,
required String target,
required Room room,
}) async {
final req = FullTextTranslationRequestModel(
text: text,
@ -235,3 +268,11 @@ class CacheItem {
CacheItem(this.parentId, this.type, this.langCode, this.data);
}
class RepresentationCacheItem {
String parentId;
String langCode;
Future<PangeaRepresentation?> data;
RepresentationCacheItem(this.parentId, this.langCode, this.data);
}

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:developer';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
import 'package:fluffychat/pangea/models/student_analytics_summary_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
@ -24,8 +25,9 @@ class MyAnalyticsController {
//PTODO - locally cache and update periodically
Future<void> handleMessage(
Room room,
RecentMessageRecord messageRecord,
) async {
RecentMessageRecord messageRecord, {
bool isEdit = false,
}) async {
try {
debugPrint("in handle message with type ${messageRecord.useType}");
if (_userId == null) {
@ -48,7 +50,7 @@ class MyAnalyticsController {
for (final event in events) {
if (event != null) {
event.handleNewMessage(messageRecord);
event.handleNewMessage(messageRecord, isEdit: isEdit);
}
}
} catch (err) {
@ -76,8 +78,9 @@ class MyAnalyticsController {
Future<void> saveConstructsMixed(
List<OneConstructUse> allUses,
String langCode,
) async {
String langCode, {
bool isEdit = false,
}) async {
try {
final Map<String, List<OneConstructUse>> aggregatedVocabUse = {};
for (final use in allUses) {
@ -94,8 +97,9 @@ class MyAnalyticsController {
saveFutures.add(
analyticsRoom.saveConstructUsesSameLemma(
uses.key,
uses.value.first.constructType!,
uses.value.first.constructType ?? ConstructType.grammar,
uses.value,
isEdit: isEdit,
),
);
}

@ -2,6 +2,7 @@ import 'dart:developer';
import 'dart:math';
import 'package:fluffychat/pangea/constants/class_default_values.dart';
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
import 'package:fluffychat/pangea/controllers/class_controller.dart';
import 'package:fluffychat/pangea/controllers/contextual_definition_controller.dart';
import 'package:fluffychat/pangea/controllers/language_controller.dart';
@ -235,6 +236,7 @@ class PangeaController {
if (!userIds.contains(BotName.byEnvironment)) {
try {
await space.invite(BotName.byEnvironment);
await space.postLoad();
await space.setPower(
BotName.byEnvironment,
ClassDefaultValues.powerLevelOfAdmin,
@ -259,4 +261,35 @@ class PangeaController {
}
}
}
Future<void> setPangeaPushRules() async {
if (!(matrixState.client.globalPushRules?.override?.any(
(element) => element.ruleId == PangeaEventTypes.textToSpeechRule,
) ??
false)) {
await matrixState.client.setPushRule(
'global',
PushRuleKind.override,
PangeaEventTypes.textToSpeechRule,
[PushRuleAction.dontNotify],
conditions: [
PushCondition(
kind: 'event_match',
key: 'content.msgtype',
pattern: MessageTypes.Audio,
),
PushCondition(
kind: 'event_match',
key: 'content.transcription.lang_code',
pattern: '*',
),
PushCondition(
kind: 'event_match',
key: 'content.transcription.text',
pattern: '*',
),
],
);
}
}
}

@ -217,4 +217,31 @@ extension PangeaClient on Client {
}
return false;
}
Future<List<String>> getEditHistory(
String roomId,
String eventId,
) async {
final Room? room = getRoomById(roomId);
final Event? editEvent = await room?.getEventById(eventId);
final String? edittedEventId =
editEvent?.content.tryGetMap('m.relates_to')?['event_id'];
if (edittedEventId == null) return [];
final Event? originalEvent = await room!.getEventById(edittedEventId);
if (originalEvent == null) return [];
final Timeline timeline = await room.getTimeline();
final List<Event> editEvents = originalEvent
.aggregatedEvents(
timeline,
RelationshipTypes.edit,
)
.sorted(
(a, b) => b.originServerTs.compareTo(a.originServerTs),
)
.toList();
editEvents.add(originalEvent);
return editEvents.slice(1).map((e) => e.eventId).toList();
}
}

@ -698,16 +698,39 @@ extension PangeaRoom on Room {
}
}
Future<List<OneConstructUse>> removeEdittedLemmas(
List<OneConstructUse> lemmaUses,
) async {
final List<String> removeUses = [];
for (final use in lemmaUses) {
if (use.msgId == null) continue;
final List<String> removeIds = await client.getEditHistory(
use.chatId,
use.msgId!,
);
removeUses.addAll(removeIds);
}
lemmaUses.removeWhere((use) => removeUses.contains(use.msgId));
final allEvents = await allConstructEvents;
for (final constructEvent in allEvents) {
await constructEvent.removeEdittedUses(removeUses, client);
}
return lemmaUses;
}
Future<void> saveConstructUsesSameLemma(
String lemma,
ConstructType type,
List<OneConstructUse> lemmaUses,
) async {
List<OneConstructUse> lemmaUses, {
bool isEdit = false,
}) async {
final ConstructEvent? localEvent = _vocabEventLocal(lemma);
if (isEdit) {
lemmaUses = await removeEdittedLemmas(lemmaUses);
}
if (localEvent == null) {
final json =
ConstructUses(lemma: lemma, type: type, uses: lemmaUses).toJson();
await client.setRoomStateWithKey(
id,
PangeaEventTypes.vocab,

@ -1,6 +1,6 @@
import 'package:fluffychat/pangea/models/constructs_analytics_model.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pangea/models/constructs_analytics_model.dart';
import '../constants/pangea_event_types.dart';
class ConstructEvent {
@ -30,4 +30,24 @@ class ConstructEvent {
content.uses.addAll(uses);
event.content = content.toJson();
}
Future<void> removeEdittedUses(
List<String> removeIds,
Client client,
) async {
_contentCache ??= ConstructUses.fromJson(event.content);
if (_contentCache == null || _event.stateKey == null) return;
final previousLength = _contentCache!.uses.length;
_contentCache!.uses.removeWhere(
(element) => removeIds.contains(element.msgId),
);
if (previousLength > _contentCache!.uses.length) {
await client.setRoomStateWithKey(
_event.room.id,
_event.type,
_event.stateKey!,
_contentCache!.toJson(),
);
}
}
}

@ -2,7 +2,6 @@ import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:fluffychat/pangea/constants/pangea_message_types.dart';
import 'package:fluffychat/pangea/controllers/text_to_speech_controller.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/models/choreo_record.dart';
@ -70,6 +69,12 @@ class PangeaMessageEvent {
.firstOrNull ??
_event;
Event updateLatestEdit() {
_latestEditCache = null;
_representations = null;
return _latestEdit;
}
bool showRichText(bool selected, bool highlighted) {
if (!_isValidPangeaMessageEvent) {
return false;
@ -466,7 +471,7 @@ class PangeaMessageEvent {
_event.room.isSpaceAdmin &&
_event.senderId != BotName.byEnvironment &&
!room.isUserSpaceAdmin(_event.senderId) &&
_event.messageType != PangeaMessageTypes.report;
_event.messageType == MessageTypes.Text;
String get messageDisplayLangCode {
final bool immersionMode = MatrixState

@ -1,12 +1,12 @@
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pangea/constants/class_default_values.dart';
import 'package:fluffychat/pangea/extensions/client_extension.dart';
import 'package:fluffychat/pangea/models/student_analytics_summary_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
import 'package:matrix/matrix.dart';
import '../constants/pangea_event_types.dart';
import 'chart_analytics_model.dart';
@ -41,8 +41,27 @@ class StudentAnalyticsEvent {
return _contentCache!;
}
Future<void> handleNewMessage(RecentMessageRecord message) async {
debugPrint("handle new message");
Future<void> removeEdittedMessages(
RecentMessageRecord message,
) async {
final List<String> removeIds = await classRoom.client.getEditHistory(
message.chatId,
message.eventId,
);
if (removeIds.isEmpty) return;
_messagesToSave.removeWhere(
(msg) => removeIds.any((e) => e == msg.eventId),
);
content.removeEdittedMessages(
classRoom.client,
removeIds,
);
}
Future<void> handleNewMessage(
RecentMessageRecord message, {
isEdit = false,
}) async {
if (classRoom.client.userID != _event.stateKey) {
debugger(when: kDebugMode);
ErrorHandler.logError(
@ -50,6 +69,10 @@ class StudentAnalyticsEvent {
);
return;
}
if (isEdit) {
await removeEdittedMessages(message);
}
_addMessage(message);
if (DateTime.now().difference(content.lastUpdated).inMinutes >
@ -66,6 +89,10 @@ class StudentAnalyticsEvent {
);
return;
}
for (final message in messages) {
await removeEdittedMessages(message);
}
_messagesToSave.addAll(messages);
_updateStudentAnalytics();
}
@ -75,6 +102,7 @@ class StudentAnalyticsEvent {
content.addAll(_messagesToSave);
debugPrint("updating student analytics");
_clearMessages();
await classRoom.client.setRoomStateWithKey(
classRoom.id,
_event.type,

@ -1,8 +1,9 @@
import 'dart:convert';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import '../enum/use_type.dart';
class RecentMessageRecord {
@ -78,6 +79,12 @@ class StudentAnalyticsSummary {
}
}
void removeEdittedMessages(Client client, List<String> removeEventIds) {
_messages.removeWhere(
(element) => removeEventIds.contains(element.eventId),
);
}
List<RecentMessageRecord> get messages => _messages;
static const _messagesKey = "msgs";

@ -49,10 +49,6 @@ class StudentAnalyticsController extends State<StudentAnalyticsPage> {
}
Future<void> initialize() async {
await _pangeaController.matrixState.client
.updateMyLearningAnalyticsForAllClassesImIn(
_pangeaController.pStoreService,
);
await getClassAndChatAnalytics(context);
stateSub = _pangeaController.matrixState.client.onRoomState.stream
.where(

@ -43,6 +43,7 @@ class GetChatListItemSubtitle {
}
if (event.type != EventTypes.Message ||
event.messageType != MessageTypes.Text ||
!pangeaController.permissionsController
.isToolEnabled(ToolSetting.immersionMode, event.room)) {
return event.calcLocalizedBody(

@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
@ -134,6 +135,13 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
future: () => add
? _addSingleSpace(room!.id, possibleParent)
: possibleParent.removeSpaceChild(room!.id),
onError: (e) {
// if error occurs, do not change value of toggle
add = !add;
return (e as Object?)?.toLocalizedString(context) ??
e?.toString() ??
L10n.of(context)!.oopsSomethingWentWrong;
},
);
}

@ -4,6 +4,7 @@ import 'dart:convert';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:collection/collection.dart';
import 'package:desktop_notifications/desktop_notifications.dart';
import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/utils/any_state_holder.dart';
import 'package:fluffychat/utils/client_manager.dart';
@ -249,8 +250,14 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
WidgetsBinding.instance.addPostFrameCallback((_) {
LoadingDialog.defaultTitle = L10n.of(context)!.loadingPleaseWait;
LoadingDialog.defaultBackLabel = L10n.of(context)!.close;
LoadingDialog.defaultOnError =
(e) => (e as Object?)!.toLocalizedString(context);
// #Pangea
// LoadingDialog.defaultOnError =
// (e) => (e as Object?)!.toLocalizedString(context);
LoadingDialog.defaultOnError = (e) =>
(e as Object?)?.toLocalizedString(context) ??
e?.toString() ??
L10n.of(context)!.oopsSomethingWentWrong;
// Pangea#
});
}
@ -354,7 +361,11 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
e.type == EventUpdateType.timeline &&
[EventTypes.Message, EventTypes.Sticker, EventTypes.Encrypted]
.contains(e.content['type']) &&
e.content['sender'] != c.userID,
e.content['sender'] != c.userID
// #Pangea
&&
!e.content['content']?.containsKey(ModelKey.transcription),
// Pangea#,
)
.listen(showLocalNotification);
});

@ -83,10 +83,7 @@ dependencies:
punycode: ^1.0.0
qr_code_scanner: ^1.0.1
receive_sharing_intent: ^1.4.5
# #Pangea
# record: 4.4.4 # Upgrade to 5 currently breaks playing on iOS
record: ^5.0.4
# Pangea#
record: 4.4.4 # Upgrade to 5 currently breaks playing on iOS
scroll_to_index: ^3.0.1
share_plus: ^8.0.2
shared_preferences: ^2.2.0 # Pinned because https://github.com/flutter/flutter/issues/118401

@ -19,7 +19,10 @@
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="The cutest messenger in the Matrix network.">
<!-- #Pangea -->
<!-- <meta name="description" content="The cutest messenger in the Matrix network."> -->
<meta name="description" content="Learn a language while texting your friends.">
<!-- Pangea# -->
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">

Loading…
Cancel
Save