Merge branch 'main' into 512-bump-matrix-sdk
This commit is contained in:
commit
e32de242d8
24 changed files with 574 additions and 341 deletions
|
|
@ -112,6 +112,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
with WidgetsBindingObserver {
|
||||
// #Pangea
|
||||
final PangeaController pangeaController = MatrixState.pangeaController;
|
||||
|
||||
late Choreographer choreographer = Choreographer(pangeaController, this);
|
||||
// Pangea#
|
||||
Room get room => sendingClient.getRoomById(roomId) ?? widget.room;
|
||||
|
|
@ -475,10 +476,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
if (kIsWeb && !Matrix.of(context).webHasFocus) return;
|
||||
// #Pangea
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
e: PangeaWarningError("Web focus error: $err"),
|
||||
s: s,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Pangea#
|
||||
|
|
|
|||
|
|
@ -83,6 +83,17 @@ class ChatEmojiPicker extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
// #Pangea
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: FloatingActionButton(
|
||||
onPressed: controller.hideEmojiPicker,
|
||||
shape: const CircleBorder(),
|
||||
mini: true,
|
||||
child: const Icon(Icons.close),
|
||||
),
|
||||
),
|
||||
// Pangea#
|
||||
],
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,14 +3,18 @@ import 'package:fluffychat/config/themes.dart';
|
|||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_app_bar_list_tile.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_app_bar_title.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_emoji_picker.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_event_list.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_input_row.dart';
|
||||
import 'package:fluffychat/pages/chat/pinned_events.dart';
|
||||
import 'package:fluffychat/pages/chat/reactions_picker.dart';
|
||||
import 'package:fluffychat/pages/chat/reply_display.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/chat_floating_action_button.dart';
|
||||
import 'package:fluffychat/utils/account_config.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/chat_settings_popup_menu.dart';
|
||||
import 'package:fluffychat/widgets/connection_status_header.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -22,10 +26,7 @@ import 'package:future_loading_dialog/future_loading_dialog.dart';
|
|||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import '../../pangea/choreographer/widgets/it_bar.dart';
|
||||
import '../../utils/stream_extension.dart';
|
||||
import 'chat_emoji_picker.dart';
|
||||
import 'chat_input_row.dart';
|
||||
|
||||
enum _EventContextAction { info, report }
|
||||
|
||||
|
|
@ -274,9 +275,6 @@ class ChatView extends StatelessWidget {
|
|||
// ),
|
||||
// )
|
||||
// : null,
|
||||
floatingActionButton: ChatFloatingActionButton(
|
||||
controller: controller,
|
||||
),
|
||||
// Pangea#
|
||||
body:
|
||||
// #Pangea
|
||||
|
|
@ -404,22 +402,32 @@ class ChatView extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const ConnectionStatusHeader(),
|
||||
ITBar(
|
||||
choreographer:
|
||||
controller.choreographer,
|
||||
),
|
||||
ReactionsPicker(controller),
|
||||
ReplyDisplay(controller),
|
||||
ChatInputRow(controller),
|
||||
ChatEmojiPicker(controller),
|
||||
],
|
||||
),
|
||||
:
|
||||
// #Pangea
|
||||
null,
|
||||
// Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// const ConnectionStatusHeader(),
|
||||
// ITBar(
|
||||
// choreographer:
|
||||
// controller.choreographer,
|
||||
// ),
|
||||
// ReactionsPicker(controller),
|
||||
// ReplyDisplay(controller),
|
||||
// ChatInputRow(controller),
|
||||
// ChatEmojiPicker(controller),
|
||||
// ],
|
||||
// ),
|
||||
// Pangea#
|
||||
),
|
||||
),
|
||||
// #Pangea
|
||||
// Keep messages above minimum input bar height
|
||||
SizedBox(
|
||||
height: (PlatformInfos.isMobile ? 30 : 60),
|
||||
),
|
||||
// Pangea#
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -436,9 +444,69 @@ class ChatView extends StatelessWidget {
|
|||
// ),
|
||||
// ),
|
||||
Positioned(
|
||||
left: 20,
|
||||
bottom: 75,
|
||||
child: StartIGCButton(controller: controller),
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 16,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (!controller.selectMode)
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: 10,
|
||||
left: bottomSheetPadding,
|
||||
right: bottomSheetPadding,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 2.4,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
StartIGCButton(
|
||||
controller: controller,
|
||||
),
|
||||
ChatFloatingActionButton(
|
||||
controller: controller,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: bottomSheetPadding,
|
||||
left: bottomSheetPadding,
|
||||
right: bottomSheetPadding,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 2.5,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceContainerHighest,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const ConnectionStatusHeader(),
|
||||
ITBar(
|
||||
choreographer: controller.choreographer,
|
||||
),
|
||||
ReactionsPicker(controller),
|
||||
ReplyDisplay(controller),
|
||||
ChatInputRow(controller),
|
||||
ChatEmojiPicker(controller),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Pangea#
|
||||
],
|
||||
|
|
|
|||
|
|
@ -289,17 +289,20 @@ class MessageContent extends StatelessWidget {
|
|||
// #Pangea
|
||||
// return Linkify(
|
||||
final messageTextStyle = TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: textColor,
|
||||
fontSize: bigEmotes ? fontSize * 3 : fontSize,
|
||||
decoration: event.redacted ? TextDecoration.lineThrough : null,
|
||||
height: 1.3,
|
||||
);
|
||||
if (immersionMode && pangeaMessageEvent != null) {
|
||||
return PangeaRichText(
|
||||
style: messageTextStyle,
|
||||
pangeaMessageEvent: pangeaMessageEvent!,
|
||||
immersionMode: immersionMode,
|
||||
toolbarController: toolbarController,
|
||||
return Flexible(
|
||||
child: PangeaRichText(
|
||||
style: messageTextStyle,
|
||||
pangeaMessageEvent: pangeaMessageEvent!,
|
||||
immersionMode: immersionMode,
|
||||
toolbarController: toolbarController,
|
||||
),
|
||||
);
|
||||
} else if (pangeaMessageEvent != null) {
|
||||
toolbarController?.toolbar?.textSelection.setMessageText(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import '../../../config/app_config.dart';
|
||||
|
||||
class ReplyContent extends StatelessWidget {
|
||||
|
|
|
|||
|
|
@ -504,6 +504,9 @@ class InputBar extends StatelessWidget {
|
|||
onSubmitted!(text);
|
||||
},
|
||||
// #Pangea
|
||||
style: controller?.isMaxLength ?? false
|
||||
? const TextStyle(color: Colors.red)
|
||||
: null,
|
||||
onTap: () {
|
||||
controller!.onInputTap(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import '../../config/themes.dart';
|
||||
import 'chat.dart';
|
||||
import 'events/reply_content.dart';
|
||||
|
|
|
|||
|
|
@ -53,6 +53,25 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
widget.controller.pangeaController.pStoreService.read(_chatCountsKey) ??
|
||||
{},
|
||||
);
|
||||
|
||||
/// Used to filter out sync updates with hierarchy updates for the active
|
||||
/// space so that the view can be auto-reloaded in the room subscription
|
||||
bool hasHierarchyUpdate(SyncUpdate update) {
|
||||
final joinTimeline =
|
||||
update.rooms?.join?[widget.controller.activeSpaceId]?.timeline;
|
||||
final leaveTimeline =
|
||||
update.rooms?.leave?[widget.controller.activeSpaceId]?.timeline;
|
||||
if (joinTimeline == null && leaveTimeline == null) return false;
|
||||
final bool hasJoinUpdate = joinTimeline?.events?.any(
|
||||
(event) => event.type == EventTypes.SpaceChild,
|
||||
) ??
|
||||
false;
|
||||
final bool hasLeaveUpdate = leaveTimeline?.events?.any(
|
||||
(event) => event.type == EventTypes.SpaceChild,
|
||||
) ??
|
||||
false;
|
||||
return hasJoinUpdate || hasLeaveUpdate;
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
@override
|
||||
|
|
@ -78,12 +97,9 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
// Listen for changes to the activeSpace's hierarchy,
|
||||
// and reload the hierarchy when they come through
|
||||
final client = Matrix.of(context).client;
|
||||
_roomSubscription ??= client.onRoomState.stream.where((u) {
|
||||
return u.state.type == EventTypes.SpaceChild &&
|
||||
u.roomId == widget.controller.activeSpaceId;
|
||||
}).listen((update) {
|
||||
loadHierarchy(hasUpdate: true);
|
||||
});
|
||||
_roomSubscription ??= client.onSync.stream
|
||||
.where(hasHierarchyUpdate)
|
||||
.listen((update) => loadHierarchy(hasUpdate: true));
|
||||
// Pangea#
|
||||
super.initState();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,13 +58,10 @@ class LanguagePermissionsButtons extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 56.0),
|
||||
child: FloatingActionButton(
|
||||
mini: true,
|
||||
child: const Icon(Icons.history_edu_outlined),
|
||||
onPressed: () => showMessage(context, text),
|
||||
),
|
||||
return FloatingActionButton(
|
||||
mini: true,
|
||||
child: const Icon(Icons.history_edu_outlined),
|
||||
onPressed: () => showMessage(context, text),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:fluffychat/pangea/constants/age_limits.dart';
|
||||
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/controllers/base_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||
|
|
@ -36,63 +35,73 @@ class PermissionsController extends BaseController {
|
|||
return dob?.isAtLeastYearsOld(AgeLimits.toAccessFeatures) ?? false;
|
||||
}
|
||||
|
||||
/// A user can private chat if
|
||||
/// 1) they are 18 and outside a class context or
|
||||
/// 2) they are in a class context and the class rules permit it
|
||||
/// If no class is passed, uses classController.activeClass
|
||||
/// A user can private chat if they are 18+
|
||||
bool canUserPrivateChat({String? roomID}) {
|
||||
final Room? classContext =
|
||||
firstRoomWithState(roomID: roomID, type: PangeaEventTypes.rules);
|
||||
return classContext?.pangeaRoomRules == null
|
||||
? isUser18()
|
||||
: classContext!.pangeaRoomRules!.oneToOneChatClass ||
|
||||
classContext.isRoomAdmin;
|
||||
return isUser18();
|
||||
// Rules can't be edited; default to true
|
||||
// final Room? classContext =
|
||||
// firstRoomWithState(roomID: roomID, type: PangeaEventTypes.rules);
|
||||
// return classContext?.pangeaRoomRules == null
|
||||
// ? isUser18()
|
||||
// : classContext!.pangeaRoomRules!.oneToOneChatClass ||
|
||||
// classContext.isRoomAdmin;
|
||||
}
|
||||
|
||||
bool canUserGroupChat({String? roomID}) {
|
||||
final Room? classContext =
|
||||
firstRoomWithState(roomID: roomID, type: PangeaEventTypes.rules);
|
||||
return isUser18();
|
||||
// Rules can't be edited; default to true
|
||||
// final Room? classContext =
|
||||
// firstRoomWithState(roomID: roomID, type: PangeaEventTypes.rules);
|
||||
|
||||
return classContext?.pangeaRoomRules == null
|
||||
? isUser18()
|
||||
: classContext!.pangeaRoomRules!.isCreateRooms ||
|
||||
classContext.isRoomAdmin;
|
||||
// return classContext?.pangeaRoomRules == null
|
||||
// ? isUser18()
|
||||
// : classContext!.pangeaRoomRules!.isCreateRooms ||
|
||||
// classContext.isRoomAdmin;
|
||||
}
|
||||
|
||||
bool showChatInputAddButton(String roomId) {
|
||||
final PangeaRoomRules? perms = _getRoomRules(roomId);
|
||||
if (perms == null) return isUser18();
|
||||
return perms.isShareFiles ||
|
||||
perms.isShareLocation ||
|
||||
perms.isSharePhoto ||
|
||||
perms.isShareVideo;
|
||||
// Rules can't be edited; default to true
|
||||
// final PangeaRoomRules? perms = _getRoomRules(roomId);
|
||||
// if (perms == null) return isUser18();
|
||||
// return perms.isShareFiles ||
|
||||
// perms.isShareLocation ||
|
||||
// perms.isSharePhoto ||
|
||||
// perms.isShareVideo;
|
||||
return isUser18();
|
||||
}
|
||||
|
||||
/// works for both roomID of chat and class
|
||||
bool canShareVideo(String? roomID) =>
|
||||
_getRoomRules(roomID)?.isShareVideo ?? isUser18();
|
||||
bool canShareVideo(String? roomID) => isUser18();
|
||||
// Rules can't be edited; default to true
|
||||
// _getRoomRules(roomID)?.isShareVideo ?? isUser18();
|
||||
|
||||
/// works for both roomID of chat and class
|
||||
bool canSharePhoto(String? roomID) =>
|
||||
_getRoomRules(roomID)?.isSharePhoto ?? isUser18();
|
||||
bool canSharePhoto(String? roomID) => isUser18();
|
||||
// Rules can't be edited; default to true
|
||||
// _getRoomRules(roomID)?.isSharePhoto ?? isUser18();
|
||||
|
||||
/// works for both roomID of chat and class
|
||||
bool canShareFile(String? roomID) =>
|
||||
_getRoomRules(roomID)?.isShareFiles ?? isUser18();
|
||||
bool canShareFile(String? roomID) => isUser18();
|
||||
// Rules can't be edited; default to true
|
||||
// _getRoomRules(roomID)?.isShareFiles ?? isUser18();
|
||||
|
||||
/// works for both roomID of chat and class
|
||||
bool canShareLocation(String? roomID) =>
|
||||
_getRoomRules(roomID)?.isShareLocation ?? isUser18();
|
||||
bool canShareLocation(String? roomID) => isUser18();
|
||||
// Rules can't be edited; default to true
|
||||
// _getRoomRules(roomID)?.isShareLocation ?? isUser18();
|
||||
|
||||
int? classLanguageToolPermission(Room room, ToolSetting setting) =>
|
||||
room.firstRules?.getToolSettings(setting);
|
||||
int? classLanguageToolPermission(Room room, ToolSetting setting) => 1;
|
||||
// Rules can't be edited; default to student choice
|
||||
// room.firstRules?.getToolSettings(setting);
|
||||
|
||||
//what happens if a room isn't in a class?
|
||||
// what happens if a room isn't in a class?
|
||||
bool isToolDisabledByClass(ToolSetting setting, Room? room) {
|
||||
if (room?.isSpaceAdmin ?? false) return false;
|
||||
final int? classPermission =
|
||||
room != null ? classLanguageToolPermission(room, setting) : 1;
|
||||
return classPermission == 0;
|
||||
return false;
|
||||
// Rules can't be edited; default to false
|
||||
// if (room?.isSpaceAdmin ?? false) return false;
|
||||
// final int? classPermission =
|
||||
// room != null ? classLanguageToolPermission(room, setting) : 1;
|
||||
// return classPermission == 0;
|
||||
}
|
||||
|
||||
bool userToolSetting(ToolSetting setting) {
|
||||
|
|
@ -117,18 +126,22 @@ class PermissionsController extends BaseController {
|
|||
}
|
||||
|
||||
bool isToolEnabled(ToolSetting setting, Room? room) {
|
||||
if (room?.isSpaceAdmin ?? false) {
|
||||
return userToolSetting(setting);
|
||||
}
|
||||
final int? classPermission =
|
||||
room != null ? classLanguageToolPermission(room, setting) : 1;
|
||||
if (classPermission == 0) return false;
|
||||
if (classPermission == 2) return true;
|
||||
// Rules can't be edited; default to true
|
||||
return userToolSetting(setting);
|
||||
// if (room?.isSpaceAdmin ?? false) {
|
||||
// return userToolSetting(setting);
|
||||
// }
|
||||
// final int? classPermission =
|
||||
// room != null ? classLanguageToolPermission(room, setting) : 1;
|
||||
// if (classPermission == 0) return false;
|
||||
// if (classPermission == 2) return true;
|
||||
// return userToolSetting(setting);
|
||||
}
|
||||
|
||||
bool isWritingAssistanceEnabled(Room? room) {
|
||||
return isToolEnabled(ToolSetting.interactiveTranslator, room) &&
|
||||
isToolEnabled(ToolSetting.interactiveGrammar, room);
|
||||
// Rules can't be edited; default to true
|
||||
return true;
|
||||
// return isToolEnabled(ToolSetting.interactiveTranslator, room) &&
|
||||
// isToolEnabled(ToolSetting.interactiveGrammar, room);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ enum ConstructUseTypeEnum {
|
|||
/// selected correctly in practice activity flow
|
||||
corPA,
|
||||
|
||||
/// encountered as distractor in practice activity flow and correctly ignored it
|
||||
ignPA,
|
||||
|
||||
/// was target construct in practice activity but user did not select correctly
|
||||
incPA,
|
||||
}
|
||||
|
|
@ -61,6 +64,8 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
return 'corPA';
|
||||
case ConstructUseTypeEnum.incPA:
|
||||
return 'incPA';
|
||||
case ConstructUseTypeEnum.ignPA:
|
||||
return 'ignPA';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,11 +76,11 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
case ConstructUseTypeEnum.wa:
|
||||
return Icons.thumb_up_sharp;
|
||||
case ConstructUseTypeEnum.corIt:
|
||||
return Icons.check;
|
||||
return Icons.translate;
|
||||
case ConstructUseTypeEnum.incIt:
|
||||
return Icons.close;
|
||||
return Icons.translate;
|
||||
case ConstructUseTypeEnum.ignIt:
|
||||
return Icons.close;
|
||||
return Icons.translate;
|
||||
case ConstructUseTypeEnum.ignIGC:
|
||||
return Icons.close;
|
||||
case ConstructUseTypeEnum.corIGC:
|
||||
|
|
@ -86,8 +91,45 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
return Icons.check;
|
||||
case ConstructUseTypeEnum.incPA:
|
||||
return Icons.close;
|
||||
case ConstructUseTypeEnum.ignPA:
|
||||
return Icons.close;
|
||||
case ConstructUseTypeEnum.unk:
|
||||
return Icons.help;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the point value for the construct use type
|
||||
/// This is used to calculate the both the total points for a user and per construct
|
||||
/// Users get slightly negative points for incorrect uses to encourage them to be more careful
|
||||
/// They get the most points for direct uses without help.
|
||||
/// They get a small amount of points for correct uses in interactions.
|
||||
/// Practice activities get a moderate amount of points.
|
||||
int get pointValue {
|
||||
switch (this) {
|
||||
case ConstructUseTypeEnum.ga:
|
||||
return 2;
|
||||
case ConstructUseTypeEnum.wa:
|
||||
return 3;
|
||||
case ConstructUseTypeEnum.corIt:
|
||||
return 1;
|
||||
case ConstructUseTypeEnum.incIt:
|
||||
return -1;
|
||||
case ConstructUseTypeEnum.ignIt:
|
||||
return 1;
|
||||
case ConstructUseTypeEnum.ignIGC:
|
||||
return 1;
|
||||
case ConstructUseTypeEnum.corIGC:
|
||||
return 2;
|
||||
case ConstructUseTypeEnum.incIGC:
|
||||
return -1;
|
||||
case ConstructUseTypeEnum.unk:
|
||||
return 0;
|
||||
case ConstructUseTypeEnum.corPA:
|
||||
return 2;
|
||||
case ConstructUseTypeEnum.incPA:
|
||||
return -1;
|
||||
case ConstructUseTypeEnum.ignPA:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ extension AnalyticsRoomExtension on Room {
|
|||
return Future.value();
|
||||
}
|
||||
|
||||
// Checks that user has permission to add child to space
|
||||
if (!canSendEvent(EventTypes.SpaceChild)) return;
|
||||
if (spaceChildren.any((sc) => sc.roomId == analyticsRoom.id)) return;
|
||||
|
||||
|
|
@ -103,17 +104,19 @@ extension AnalyticsRoomExtension on Room {
|
|||
.where((teacher) => !participants.contains(teacher))
|
||||
.toList();
|
||||
|
||||
Future.wait(
|
||||
uninvitedTeachers.map(
|
||||
(teacher) => analyticsRoom.invite(teacher.id).catchError((err, s) {
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
m: "Failed to invite teacher ${teacher.id} to analytics room ${analyticsRoom.id}",
|
||||
s: s,
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
if (analyticsRoom.canSendEvent(EventTypes.RoomMember)) {
|
||||
Future.wait(
|
||||
uninvitedTeachers.map(
|
||||
(teacher) => analyticsRoom.invite(teacher.id).catchError((err, s) {
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
m: "Failed to invite teacher ${teacher.id} to analytics room ${analyticsRoom.id}",
|
||||
s: s,
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invite all the user's teachers to 1 analytics room.
|
||||
|
|
@ -200,30 +203,66 @@ extension AnalyticsRoomExtension on Room {
|
|||
creationContent?.tryGet<String>(ModelKey.oldLangCode) == langCode;
|
||||
}
|
||||
|
||||
Future<String?> sendSummaryAnalyticsEvent(
|
||||
Future<void> sendSummaryAnalyticsEvent(
|
||||
List<RecentMessageRecord> records,
|
||||
) async {
|
||||
final SummaryAnalyticsModel analyticsModel = SummaryAnalyticsModel(
|
||||
messages: records,
|
||||
);
|
||||
final String? eventId = await sendEvent(
|
||||
await sendEvent(
|
||||
analyticsModel.toJson(),
|
||||
type: PangeaEventTypes.summaryAnalytics,
|
||||
);
|
||||
return eventId;
|
||||
}
|
||||
|
||||
Future<String?> sendConstructsEvent(
|
||||
/// Sends construct events to the server.
|
||||
///
|
||||
/// The [uses] parameter is a list of [OneConstructUse] objects representing the
|
||||
/// constructs to be sent. To prevent hitting the maximum event size, the events
|
||||
/// are chunked into smaller lists. Each chunk is sent as a separate event.
|
||||
Future<void> sendConstructsEvent(
|
||||
List<OneConstructUse> uses,
|
||||
) async {
|
||||
final ConstructAnalyticsModel constructsModel = ConstructAnalyticsModel(
|
||||
uses: uses,
|
||||
);
|
||||
// these events can get big, so we chunk them to prevent hitting the max event size.
|
||||
// go through each of the uses being sent and add them to the current chunk until
|
||||
// the size (in bytes) of the current chunk is greater than the max event size, then
|
||||
// start a new chunk until all uses have been added.
|
||||
final List<List<OneConstructUse>> useChunks = [];
|
||||
List<OneConstructUse> currentChunk = [];
|
||||
int currentChunkSize = 0;
|
||||
|
||||
final String? eventId = await sendEvent(
|
||||
constructsModel.toJson(),
|
||||
type: PangeaEventTypes.construct,
|
||||
);
|
||||
return eventId;
|
||||
for (final use in uses) {
|
||||
// get the size, in bytes, of the json representation of the use
|
||||
final json = use.toJson();
|
||||
final jsonString = jsonEncode(json);
|
||||
final jsonSizeInBytes = utf8.encode(jsonString).length;
|
||||
|
||||
// If this use would tip this chunk over the size limit,
|
||||
// add it to the list of all chunks and start a new chunk.
|
||||
//
|
||||
// I tested with using the maxPDUSize constant, but the events
|
||||
// were still too large. 50000 seems to be a safe number of bytes.
|
||||
if (currentChunkSize + jsonSizeInBytes > (maxPDUSize - 10000)) {
|
||||
useChunks.add(currentChunk);
|
||||
currentChunk = [];
|
||||
currentChunkSize = 0;
|
||||
}
|
||||
|
||||
// add this use to the current chunk
|
||||
currentChunk.add(use);
|
||||
currentChunkSize += jsonSizeInBytes;
|
||||
}
|
||||
|
||||
if (currentChunk.isNotEmpty) {
|
||||
useChunks.add(currentChunk);
|
||||
}
|
||||
|
||||
for (final chunk in useChunks) {
|
||||
final constructsModel = ConstructAnalyticsModel(uses: chunk);
|
||||
await sendEvent(
|
||||
constructsModel.toJson(),
|
||||
type: PangeaEventTypes.construct,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,6 +187,8 @@ class VocabTotals {
|
|||
break;
|
||||
case ConstructUseTypeEnum.unk:
|
||||
break;
|
||||
case ConstructUseTypeEnum.ignPA:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,36 +13,40 @@ class AnalyticsViewButton extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopupMenuButton<BarChartViewSelection>(
|
||||
tooltip: L10n.of(context)!.changeAnalyticsView,
|
||||
initialValue: value,
|
||||
onSelected: (BarChartViewSelection? view) {
|
||||
if (view == null) {
|
||||
debugPrint("when is view null?");
|
||||
return;
|
||||
}
|
||||
onChange(view);
|
||||
},
|
||||
itemBuilder: (BuildContext context) => BarChartViewSelection.values
|
||||
.map<PopupMenuEntry<BarChartViewSelection>>(
|
||||
(BarChartViewSelection view) {
|
||||
return PopupMenuItem<BarChartViewSelection>(
|
||||
value: view,
|
||||
child: Text(view.string(context)),
|
||||
);
|
||||
}).toList(),
|
||||
child: TextButton.icon(
|
||||
label: Text(
|
||||
value.string(context),
|
||||
style: TextStyle(
|
||||
return Flexible(
|
||||
child: PopupMenuButton<BarChartViewSelection>(
|
||||
tooltip: L10n.of(context)!.changeAnalyticsView,
|
||||
initialValue: value,
|
||||
onSelected: (BarChartViewSelection? view) {
|
||||
if (view == null) {
|
||||
debugPrint("when is view null?");
|
||||
return;
|
||||
}
|
||||
onChange(view);
|
||||
},
|
||||
itemBuilder: (BuildContext context) => BarChartViewSelection.values
|
||||
.map<PopupMenuEntry<BarChartViewSelection>>(
|
||||
(BarChartViewSelection view) {
|
||||
return PopupMenuItem<BarChartViewSelection>(
|
||||
value: view,
|
||||
child: Text(
|
||||
view.string(context),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
child: TextButton.icon(
|
||||
label: Text(
|
||||
value.string(context),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
icon: Icon(
|
||||
value.icon,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
onPressed: null,
|
||||
),
|
||||
icon: Icon(
|
||||
value.icon,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
onPressed: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,35 +14,37 @@ class TimeSpanMenuButton extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopupMenuButton<TimeSpan>(
|
||||
tooltip: L10n.of(context)!.changeDateRange,
|
||||
initialValue: value,
|
||||
onSelected: (TimeSpan? timeSpan) {
|
||||
if (timeSpan == null) {
|
||||
debugPrint("when is timeSpan null?");
|
||||
return;
|
||||
}
|
||||
onChange(timeSpan);
|
||||
},
|
||||
itemBuilder: (BuildContext context) =>
|
||||
TimeSpan.values.map<PopupMenuEntry<TimeSpan>>((TimeSpan timeSpan) {
|
||||
return PopupMenuItem<TimeSpan>(
|
||||
value: timeSpan,
|
||||
child: Text(timeSpan.string(context)),
|
||||
);
|
||||
}).toList(),
|
||||
child: TextButton.icon(
|
||||
label: Text(
|
||||
value.string(context),
|
||||
style: TextStyle(
|
||||
return Flexible(
|
||||
child: PopupMenuButton<TimeSpan>(
|
||||
tooltip: L10n.of(context)!.changeDateRange,
|
||||
initialValue: value,
|
||||
onSelected: (TimeSpan? timeSpan) {
|
||||
if (timeSpan == null) {
|
||||
debugPrint("when is timeSpan null?");
|
||||
return;
|
||||
}
|
||||
onChange(timeSpan);
|
||||
},
|
||||
itemBuilder: (BuildContext context) =>
|
||||
TimeSpan.values.map<PopupMenuEntry<TimeSpan>>((TimeSpan timeSpan) {
|
||||
return PopupMenuItem<TimeSpan>(
|
||||
value: timeSpan,
|
||||
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,
|
||||
),
|
||||
icon: Icon(
|
||||
Icons.calendar_month_outlined,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
onPressed: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
/// Utility to save and read data both in the matrix profile (this is the default
|
||||
/// behavior) and in the local storage (local needs to be specificied). An
|
||||
|
|
@ -66,6 +67,9 @@ class PStore {
|
|||
|
||||
/// Clears the storage by erasing all data in the box.
|
||||
void clearStorage() {
|
||||
// this could potenitally be interfering with openning database
|
||||
// at the start of the session, which is causing auto log outs on iOS
|
||||
Sentry.addBreadcrumb(Breadcrumb(message: 'Clearing local storage'));
|
||||
_box.erase();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,14 +67,11 @@ class ChatFloatingActionButtonState extends State<ChatFloatingActionButton> {
|
|||
return const SizedBox.shrink();
|
||||
}
|
||||
if (widget.controller.showScrollDownButton) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 56.0),
|
||||
child: FloatingActionButton(
|
||||
onPressed: widget.controller.scrollDown,
|
||||
heroTag: null,
|
||||
mini: true,
|
||||
child: const Icon(Icons.arrow_downward_outlined),
|
||||
),
|
||||
return FloatingActionButton(
|
||||
onPressed: widget.controller.scrollDown,
|
||||
heroTag: null,
|
||||
mini: true,
|
||||
child: const Icon(Icons.arrow_downward_outlined),
|
||||
);
|
||||
}
|
||||
if (widget.controller.choreographer.errorService.error != null) {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ class InputBarWrapper extends StatefulWidget {
|
|||
|
||||
class InputBarWrapperState extends State<InputBarWrapper> {
|
||||
StreamSubscription? _choreoSub;
|
||||
String _currentText = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -61,6 +62,24 @@ class InputBarWrapperState extends State<InputBarWrapper> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
void refreshOnChange(String text) {
|
||||
if (widget.onChanged != null) {
|
||||
widget.onChanged!(text);
|
||||
}
|
||||
|
||||
final bool decreasedFromMaxLength =
|
||||
_currentText.length >= PangeaTextController.maxLength &&
|
||||
text.length < PangeaTextController.maxLength;
|
||||
final bool reachedMaxLength =
|
||||
_currentText.length < PangeaTextController.maxLength &&
|
||||
text.length < PangeaTextController.maxLength;
|
||||
|
||||
if (decreasedFromMaxLength || reachedMaxLength) {
|
||||
setState(() {});
|
||||
}
|
||||
_currentText = text;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InputBar(
|
||||
|
|
@ -73,7 +92,7 @@ class InputBarWrapperState extends State<InputBarWrapper> {
|
|||
focusNode: widget.focusNode,
|
||||
controller: widget.controller,
|
||||
decoration: widget.decoration,
|
||||
onChanged: widget.onChanged,
|
||||
onChanged: refreshOnChange,
|
||||
autofocus: widget.autofocus,
|
||||
textInputAction: widget.textInputAction,
|
||||
readOnly: widget.readOnly,
|
||||
|
|
|
|||
|
|
@ -58,10 +58,9 @@ class ToolbarDisplayController {
|
|||
);
|
||||
}
|
||||
|
||||
void showToolbar(
|
||||
BuildContext context, {
|
||||
MessageMode? mode,
|
||||
}) {
|
||||
void showToolbar(BuildContext context, {MessageMode? mode}) {
|
||||
// Close keyboard, if open
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
bool toolbarUp = true;
|
||||
if (highlighted) return;
|
||||
if (controller.selectMode) {
|
||||
|
|
@ -87,12 +86,13 @@ class ToolbarDisplayController {
|
|||
if (targetOffset.dy < 320) {
|
||||
final spaceBeneath = MediaQuery.of(context).size.height -
|
||||
(targetOffset.dy + transformTargetSize.height);
|
||||
if (spaceBeneath >= 320) {
|
||||
toolbarUp = false;
|
||||
}
|
||||
// If toolbar is open, opening toolbar beneath without scrolling can cause issues
|
||||
// if (spaceBeneath >= 320) {
|
||||
// toolbarUp = false;
|
||||
// }
|
||||
|
||||
// See if it's possible to scroll up to make space
|
||||
else if (controller.scrollController.offset - targetOffset.dy + 320 >=
|
||||
if (controller.scrollController.offset - targetOffset.dy + 320 >=
|
||||
controller.scrollController.position.minScrollExtent &&
|
||||
controller.scrollController.offset - targetOffset.dy + 320 <=
|
||||
controller.scrollController.position.maxScrollExtent) {
|
||||
|
|
@ -152,13 +152,7 @@ class ToolbarDisplayController {
|
|||
? CrossAxisAlignment.end
|
||||
: CrossAxisAlignment.start,
|
||||
children: [
|
||||
toolbarUp
|
||||
// Column is limited to screen height
|
||||
// If message portion is too tall, decrease toolbar height
|
||||
// as necessary to prevent toolbar from acting strange
|
||||
// Problems may still occur if toolbar height is decreased too much
|
||||
? toolbar!
|
||||
: overlayMessage,
|
||||
toolbarUp ? toolbar! : overlayMessage,
|
||||
const SizedBox(height: 6),
|
||||
toolbarUp ? overlayMessage : toolbar!,
|
||||
],
|
||||
|
|
@ -419,85 +413,83 @@ class MessageToolbarState extends State<MessageToolbar> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Flexible(
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
border: Border.all(
|
||||
width: 2,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(25),
|
||||
),
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
border: Border.all(
|
||||
width: 2,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 300,
|
||||
minWidth: 300,
|
||||
maxHeight: 300,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(25),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: toolbarContent ?? const SizedBox(),
|
||||
),
|
||||
SizedBox(height: toolbarContent == null ? 0 : 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 300,
|
||||
minWidth: 300,
|
||||
maxHeight: 300,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: toolbarContent ?? const SizedBox(),
|
||||
),
|
||||
SizedBox(height: toolbarContent == null ? 0 : 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: MessageMode.values.map((mode) {
|
||||
if ([
|
||||
MessageMode.definition,
|
||||
MessageMode.textToSpeech,
|
||||
MessageMode.translation,
|
||||
].contains(mode) &&
|
||||
widget.pangeaMessageEvent.isAudioMessage) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
if (mode == MessageMode.speechToText &&
|
||||
!widget.pangeaMessageEvent.isAudioMessage) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Tooltip(
|
||||
message: mode.tooltip(context),
|
||||
child: IconButton(
|
||||
icon: Icon(mode.icon),
|
||||
color: mode.iconColor(
|
||||
widget.pangeaMessageEvent,
|
||||
currentMode,
|
||||
context,
|
||||
),
|
||||
onPressed: () => updateMode(mode),
|
||||
),
|
||||
);
|
||||
}).toList() +
|
||||
[
|
||||
Tooltip(
|
||||
message: L10n.of(context)!.more,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.add_reaction_outlined),
|
||||
onPressed: showMore,
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: MessageMode.values.map((mode) {
|
||||
if ([
|
||||
MessageMode.definition,
|
||||
MessageMode.textToSpeech,
|
||||
MessageMode.translation,
|
||||
].contains(mode) &&
|
||||
widget.pangeaMessageEvent.isAudioMessage) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
if (mode == MessageMode.speechToText &&
|
||||
!widget.pangeaMessageEvent.isAudioMessage) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Tooltip(
|
||||
message: mode.tooltip(context),
|
||||
child: IconButton(
|
||||
icon: Icon(mode.icon),
|
||||
color: mode.iconColor(
|
||||
widget.pangeaMessageEvent,
|
||||
currentMode,
|
||||
context,
|
||||
),
|
||||
onPressed: () => updateMode(mode),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList() +
|
||||
[
|
||||
Tooltip(
|
||||
message: L10n.of(context)!.more,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.add_reaction_outlined),
|
||||
onPressed: showMore,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -118,81 +118,85 @@ class OverlayMessage extends StatelessWidget {
|
|||
ownMessage: ownMessage,
|
||||
);
|
||||
|
||||
return Material(
|
||||
color: noBubble ? Colors.transparent : color,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius,
|
||||
),
|
||||
return Flexible(
|
||||
child: Material(
|
||||
color: noBubble ? Colors.transparent : color,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
padding: noBubble || noPadding
|
||||
? EdgeInsets.zero
|
||||
: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: width ?? FluffyThemes.columnWidth * 1.25,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MessageContent(
|
||||
event.getDisplayEvent(timeline),
|
||||
textColor: textColor,
|
||||
borderRadius: borderRadius,
|
||||
selected: selected,
|
||||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
immersionMode: immersionMode,
|
||||
toolbarController: toolbarController,
|
||||
isOverlay: true,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius,
|
||||
),
|
||||
if (event.hasAggregatedEvents(
|
||||
timeline,
|
||||
RelationshipTypes.edit,
|
||||
) ||
|
||||
(pangeaMessageEvent.showUseType))
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 4.0,
|
||||
),
|
||||
padding: noBubble || noPadding
|
||||
? EdgeInsets.zero
|
||||
: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (pangeaMessageEvent.showUseType) ...[
|
||||
pangeaMessageEvent.msgUseType.iconView(
|
||||
context,
|
||||
textColor.withAlpha(164),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
if (event.hasAggregatedEvents(
|
||||
timeline,
|
||||
RelationshipTypes.edit,
|
||||
)) ...[
|
||||
Icon(
|
||||
Icons.edit_outlined,
|
||||
color: textColor.withAlpha(164),
|
||||
size: 14,
|
||||
),
|
||||
Text(
|
||||
' - ${event.getDisplayEvent(timeline).originServerTs.localizedTimeShort(context)}',
|
||||
style: TextStyle(
|
||||
color: textColor.withAlpha(164),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: width ?? FluffyThemes.columnWidth * 1.25,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: MessageContent(
|
||||
event.getDisplayEvent(timeline),
|
||||
textColor: textColor,
|
||||
borderRadius: borderRadius,
|
||||
selected: selected,
|
||||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
immersionMode: immersionMode,
|
||||
toolbarController: toolbarController,
|
||||
isOverlay: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (event.hasAggregatedEvents(
|
||||
timeline,
|
||||
RelationshipTypes.edit,
|
||||
) ||
|
||||
(pangeaMessageEvent.showUseType))
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 4.0,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (pangeaMessageEvent.showUseType) ...[
|
||||
pangeaMessageEvent.msgUseType.iconView(
|
||||
context,
|
||||
textColor.withAlpha(164),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
if (event.hasAggregatedEvents(
|
||||
timeline,
|
||||
RelationshipTypes.edit,
|
||||
)) ...[
|
||||
Icon(
|
||||
Icons.edit_outlined,
|
||||
color: textColor.withAlpha(164),
|
||||
size: 14,
|
||||
),
|
||||
Text(
|
||||
' - ${event.getDisplayEvent(timeline).originServerTs.localizedTimeShort(context)}',
|
||||
style: TextStyle(
|
||||
color: textColor.withAlpha(164),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ class PangeaTextController extends TextEditingController {
|
|||
text ??= '';
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
static const int maxLength = 1000;
|
||||
bool get isMaxLength => text.length == 1000;
|
||||
|
||||
bool forceKeepOpen = false;
|
||||
|
||||
setSystemText(String text, EditType type) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:matrix/matrix.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
|
||||
|
|
@ -80,6 +81,9 @@ Future<MatrixSdkDatabase> _constructDatabase(Client client) async {
|
|||
}
|
||||
|
||||
final cipher = await getDatabaseCipher();
|
||||
// #Pangea
|
||||
Sentry.addBreadcrumb(Breadcrumb(message: 'Database cipher: $cipher'));
|
||||
// Pangea#
|
||||
|
||||
Directory? fileStorageLocation;
|
||||
try {
|
||||
|
|
@ -97,6 +101,9 @@ Future<MatrixSdkDatabase> _constructDatabase(Client client) async {
|
|||
// import the SQLite / SQLCipher shared objects / dynamic libraries
|
||||
final factory =
|
||||
createDatabaseFactoryFfi(ffiInit: SQfLiteEncryptionHelper.ffiInit);
|
||||
// #Pangea
|
||||
Sentry.addBreadcrumb(Breadcrumb(message: 'Database path: $path'));
|
||||
// Pangea#
|
||||
|
||||
// migrate from potential previous SQLite database path to current one
|
||||
await _migrateLegacyLocation(path, client.clientName);
|
||||
|
|
@ -113,6 +120,9 @@ Future<MatrixSdkDatabase> _constructDatabase(Client client) async {
|
|||
path: path,
|
||||
cipher: cipher,
|
||||
);
|
||||
// #Pangea
|
||||
Sentry.addBreadcrumb(Breadcrumb(message: 'Database cipher helper: $helper'));
|
||||
// Pangea#
|
||||
|
||||
// check whether the DB is already encrypted and otherwise do so
|
||||
await helper?.ensureDatabaseFileEncrypted();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:fluffychat/config/setting_keys.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
const _passwordStorageKey = 'database_password';
|
||||
|
|
@ -58,6 +59,12 @@ void _sendNoEncryptionWarning(Object exception) async {
|
|||
// l10n.noDatabaseEncryption,
|
||||
// exception.toString(),
|
||||
// );
|
||||
Sentry.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message: 'No database encryption',
|
||||
data: {'exception': exception},
|
||||
),
|
||||
);
|
||||
// Pangea#
|
||||
|
||||
await store.setBool(SettingKeys.noEncryptionWarningShown, true);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue