Merge branch 'main' into cli-sdk-update
This commit is contained in:
commit
e91c2d9c8c
102 changed files with 2913 additions and 3006 deletions
|
|
@ -4068,6 +4068,25 @@
|
|||
"hintTitle": "Hint:",
|
||||
"speechToTextBody": "See how well you did by looking at your Accuracy and Words Per Minute scores",
|
||||
"previous": "Previous",
|
||||
"versionNotFound": "Version Not Found",
|
||||
"fetchingVersion": "Fetching version...",
|
||||
"versionFetchError": "Error fetching version",
|
||||
"connectedToStaging": "Connected to Staging",
|
||||
"versionText": "Version: {version}+{buildNumber}",
|
||||
"@versionText": {
|
||||
"description": "Text displaying the app version and build number.",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"version": {
|
||||
"type": "String",
|
||||
"description": "The current version of the app."
|
||||
},
|
||||
"buildNumber": {
|
||||
"type": "String",
|
||||
"description": "The build number of the app."
|
||||
}
|
||||
}
|
||||
},
|
||||
"languageButtonLabel": "Language: {currentLanguage}",
|
||||
"@languageButtonLabel": {
|
||||
"type": "text",
|
||||
|
|
@ -4085,5 +4104,12 @@
|
|||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"changeAnalyticsView": "Change Analytics View"
|
||||
"changeAnalyticsView": "Change Analytics View",
|
||||
"l1TranslationBody": "Oops! It looks like this message wasn't sent in your target language. Messages not sent in your target language will not be translated.",
|
||||
"continueText": "Continue",
|
||||
"deleteSubscriptionWarningTitle": "You have an active subscription",
|
||||
"deleteSubscriptionWarningBody": "Deleting your account will not automatically cancel your subscription.",
|
||||
"manageSubscription": "Manage Subscription",
|
||||
"createSpace": "Create space",
|
||||
"createChat": "Create chat"
|
||||
}
|
||||
|
|
@ -4717,5 +4717,25 @@
|
|||
"addChatToSpaceDesc": "Añadir un chat a un espacio hará que el chat aparezca dentro del espacio para los estudiantes y les dará acceso.",
|
||||
"addSpaceToSpaceDesc": "Añadir un espacio a otro espacio hará que el espacio hijo aparezca dentro del espacio padre para los estudiantes y les dará acceso.",
|
||||
"spaceAnalytics": "Analítica espacial",
|
||||
"changeAnalyticsLanguage": "Cambiar el lenguaje analítico"
|
||||
"changeAnalyticsLanguage": "Cambiar el lenguaje analítico",
|
||||
"versionNotFound": "Versión no encontrada",
|
||||
"fetchingVersion": "Obteniendo versión...",
|
||||
"versionFetchError": "Error al obtener la versión",
|
||||
"connectedToStaging": "Conectado al entorno de pruebas",
|
||||
"connectedToStaging": "Conectado al entorno de pruebas",
|
||||
"versionText": "Versión: {version}+{buildNumber}",
|
||||
"@versionText": {
|
||||
"description": "Texto que muestra la versión y el número de compilación de la aplicación.",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"version": {
|
||||
"type": "String",
|
||||
"description": "La versión actual de la aplicación."
|
||||
},
|
||||
"buildNumber": {
|
||||
"type": "String",
|
||||
"description": "El número de compilación de la aplicación."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -42,9 +42,6 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../pangea/pages/analytics/space_analytics/space_analytics.dart';
|
||||
import '../pangea/pages/analytics/space_list/space_list.dart';
|
||||
|
||||
abstract class AppRoutes {
|
||||
static FutureOr<String?> loggedInRedirect(
|
||||
BuildContext context,
|
||||
|
|
@ -176,27 +173,27 @@ abstract class AppRoutes {
|
|||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'analytics',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const AnalyticsSpaceList(),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: ':spaceid',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const SpaceAnalyticsPage(
|
||||
selectedView: BarChartViewSelection.messages,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// GoRoute(
|
||||
// path: 'analytics',
|
||||
// pageBuilder: (context, state) => defaultPageBuilder(
|
||||
// context,
|
||||
// state,
|
||||
// const AnalyticsSpaceList(),
|
||||
// ),
|
||||
// redirect: loggedOutRedirect,
|
||||
// routes: [
|
||||
// GoRoute(
|
||||
// path: ':spaceid',
|
||||
// pageBuilder: (context, state) => defaultPageBuilder(
|
||||
// context,
|
||||
// state,
|
||||
// const SpaceAnalyticsPage(
|
||||
// selectedView: BarChartViewSelection.messages,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// Pangea#
|
||||
GoRoute(
|
||||
path: 'archive',
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_e
|
|||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/models/choreo_record.dart';
|
||||
import 'package:fluffychat/pangea/models/representation_content_model.dart';
|
||||
import 'package:fluffychat/pangea/models/space_model.dart';
|
||||
import 'package:fluffychat/pangea/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
|
||||
|
|
@ -43,6 +42,7 @@ import 'package:go_router/go_router.dart';
|
|||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
|
||||
|
|
@ -293,10 +293,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
}
|
||||
|
||||
// #Pangea
|
||||
bool showPermissionsError = false;
|
||||
// #Pangea
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
scrollController.addListener(_updateScrollController);
|
||||
|
|
@ -326,31 +322,12 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
context,
|
||||
() => Future.delayed(
|
||||
Duration.zero,
|
||||
() => setState(
|
||||
() {},
|
||||
),
|
||||
() => setState(() {}),
|
||||
),
|
||||
);
|
||||
}
|
||||
await Matrix.of(context).client.roomsLoading;
|
||||
choreographer.setRoomId(roomId);
|
||||
choreographer.messageOptions.resetSelectedDisplayLang();
|
||||
choreographer.stateListener.stream.listen((event) {
|
||||
debugPrint("chat.dart choreo event $event");
|
||||
setState(() {});
|
||||
});
|
||||
showPermissionsError = !pangeaController.permissionsController
|
||||
.isToolEnabled(ToolSetting.interactiveTranslator, room) ||
|
||||
!pangeaController.permissionsController
|
||||
.isToolEnabled(ToolSetting.interactiveGrammar, room);
|
||||
});
|
||||
|
||||
Future.delayed(
|
||||
const Duration(seconds: 5),
|
||||
() {
|
||||
if (mounted) setState(() => showPermissionsError = false);
|
||||
},
|
||||
);
|
||||
// Pangea#
|
||||
_tryLoadTimeline();
|
||||
if (kIsWeb) {
|
||||
|
|
@ -439,7 +416,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
onInsert: onInsert,
|
||||
);
|
||||
// #Pangea
|
||||
if (visibleEvents.length < 10) {
|
||||
if (visibleEvents.length < 10 && timeline != null) {
|
||||
int prevNumEvents = timeline!.events.length;
|
||||
await requestHistory();
|
||||
int numRequests = 0;
|
||||
|
|
@ -497,7 +474,10 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
if (kIsWeb && !Matrix.of(context).webHasFocus) return;
|
||||
// #Pangea
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(e: err, s: s);
|
||||
ErrorHandler.logError(
|
||||
e: PangeaWarningError("Web focus error: $err"),
|
||||
s: s,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Pangea#
|
||||
|
|
@ -507,7 +487,15 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
|
||||
final timeline = this.timeline;
|
||||
if (timeline == null || timeline.events.isEmpty) return;
|
||||
if (timeline == null || timeline.events.isEmpty) {
|
||||
// #Pangea
|
||||
ErrorHandler.logError(
|
||||
e: PangeaWarningError("Timeline is null or empty"),
|
||||
s: StackTrace.current,
|
||||
);
|
||||
// Pangea#
|
||||
return;
|
||||
}
|
||||
|
||||
Logs().d('Set read marker...', eventId);
|
||||
// ignore: unawaited_futures
|
||||
|
|
@ -518,7 +506,28 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
)
|
||||
.then((_) {
|
||||
_setReadMarkerFuture = null;
|
||||
})
|
||||
// #Pangea
|
||||
.catchError((e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: PangeaWarningError("Failed to set read marker: $e"),
|
||||
s: s,
|
||||
m: 'Failed to set read marker for eventId: $eventId',
|
||||
);
|
||||
Sentry.captureException(
|
||||
e,
|
||||
stackTrace: s,
|
||||
withScope: (scope) {
|
||||
scope.setExtra(
|
||||
'extra_info',
|
||||
'Failed during setReadMarker with eventId: $eventId',
|
||||
);
|
||||
scope.setTag('where', 'setReadMarker');
|
||||
},
|
||||
);
|
||||
});
|
||||
// Pangea#
|
||||
|
||||
if (eventId == null || eventId == timeline.room.lastEvent?.eventId) {
|
||||
Matrix.of(context).backgroundPush?.cancelNotification(roomId);
|
||||
}
|
||||
|
|
@ -569,10 +578,9 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
});
|
||||
|
||||
// #Pangea
|
||||
final List<String> edittingEvents = [];
|
||||
void clearEdittingEvent(String eventId) {
|
||||
edittingEvents.remove(eventId);
|
||||
setState(() {});
|
||||
Event? pangeaEditingEvent;
|
||||
void clearEditingEvent() {
|
||||
pangeaEditingEvent = null;
|
||||
}
|
||||
|
||||
// Future<void> send() async {
|
||||
|
|
@ -632,11 +640,9 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
.then(
|
||||
(String? msgEventId) async {
|
||||
// #Pangea
|
||||
setState(() {
|
||||
if (previousEdit != null) {
|
||||
edittingEvents.add(previousEdit.eventId);
|
||||
}
|
||||
});
|
||||
if (previousEdit != null) {
|
||||
pangeaEditingEvent = previousEdit;
|
||||
}
|
||||
|
||||
GoogleAnalytics.sendMessage(
|
||||
room.id,
|
||||
|
|
@ -1229,9 +1235,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
void clearSelectedEvents() => setState(() {
|
||||
selectedEvents.clear();
|
||||
showEmojiPicker = false;
|
||||
//#Pangea
|
||||
choreographer.messageOptions.resetSelectedDisplayLang();
|
||||
//Pangea#
|
||||
});
|
||||
|
||||
void clearSingleSelectedEvent() {
|
||||
|
|
@ -1303,19 +1306,19 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
// Pangea#
|
||||
if (!event.redacted) {
|
||||
// #Pangea
|
||||
// If previous selectedEvent has same eventId, delete previous selectedEvent
|
||||
final matches =
|
||||
selectedEvents.where((e) => e.eventId == event.eventId).toList();
|
||||
// if (selectedEvents.contains(event)) {
|
||||
// setState(
|
||||
// () => selectedEvents.remove(event),
|
||||
// );
|
||||
// }
|
||||
|
||||
// If delete first selected event with the selected eventID
|
||||
final matches = selectedEvents.where((e) => e.eventId == event.eventId);
|
||||
if (matches.isNotEmpty) {
|
||||
// if (selectedEvents.contains(event)) {
|
||||
// Pangea#
|
||||
setState(
|
||||
// #Pangea
|
||||
() => selectedEvents.remove(matches.first),
|
||||
// () => selectedEvents.remove(event),
|
||||
// Pangea#
|
||||
);
|
||||
} else {
|
||||
setState(() => selectedEvents.remove(matches.first));
|
||||
}
|
||||
// Pangea#
|
||||
else {
|
||||
setState(
|
||||
() => selectedEvents.add(event),
|
||||
);
|
||||
|
|
@ -1524,35 +1527,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
});
|
||||
|
||||
// #Pangea
|
||||
double? availableSpace;
|
||||
double? inputRowSize;
|
||||
bool? lastState;
|
||||
bool get isRowScrollable {
|
||||
if (availableSpace == null || inputRowSize == null) {
|
||||
if (lastState == null) {
|
||||
lastState = false;
|
||||
Future.delayed(Duration.zero, () {
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const double offSetValue = 10;
|
||||
final bool currentState = inputRowSize! > (availableSpace! - offSetValue);
|
||||
if (!lastState! && currentState) {
|
||||
Future.delayed(Duration.zero, () {
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
if (lastState! && !currentState) {
|
||||
Future.delayed(Duration.zero, () {
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
lastState = currentState;
|
||||
return currentState;
|
||||
}
|
||||
|
||||
final Map<String, PangeaMessageEvent> _pangeaMessageEvents = {};
|
||||
final Map<String, ToolbarDisplayController> _toolbarDisplayControllers = {};
|
||||
|
||||
|
|
|
|||
|
|
@ -170,8 +170,6 @@ class ChatEventList extends StatelessWidget {
|
|||
controller.scrollToEventId(eventId),
|
||||
longPressSelect: controller.selectedEvents.isNotEmpty,
|
||||
// #Pangea
|
||||
selectedDisplayLang:
|
||||
controller.choreographer.messageOptions.selectedDisplayLang,
|
||||
immersionMode: controller.choreographer.immersionMode,
|
||||
definitions: controller.choreographer.definitionsEnabled,
|
||||
controller: controller,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:fluffychat/config/app_config.dart';
|
|||
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/send_button.dart';
|
||||
import 'package:fluffychat/pangea/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/input_bar_wrapper.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -12,7 +13,6 @@ import 'package:matrix/matrix.dart';
|
|||
|
||||
import '../../config/themes.dart';
|
||||
import 'chat.dart';
|
||||
import 'input_bar.dart';
|
||||
|
||||
class ChatInputRow extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
|
|
@ -322,7 +322,10 @@ class ChatInputRow extends StatelessWidget {
|
|||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 0.0),
|
||||
child: InputBar(
|
||||
// #Pangea
|
||||
// child: InputBar(
|
||||
child: InputBarWrapper(
|
||||
// Pangea#
|
||||
room: controller.room,
|
||||
minLines: 1,
|
||||
maxLines: 8,
|
||||
|
|
|
|||
|
|
@ -7,11 +7,9 @@ import 'package:fluffychat/pages/chat/chat_event_list.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/has_error_button.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/language_permissions_warning_buttons.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/pages/class_analytics/measure_able.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/chat_floating_action_button.dart';
|
||||
import 'package:fluffychat/utils/account_config.dart';
|
||||
import 'package:fluffychat/widgets/chat_settings_popup_menu.dart';
|
||||
import 'package:fluffychat/widgets/connection_status_header.dart';
|
||||
|
|
@ -266,32 +264,20 @@ class ChatView extends StatelessWidget {
|
|||
// #Pangea
|
||||
// floatingActionButton: controller.showScrollDownButton &&
|
||||
// controller.selectedEvents.isEmpty
|
||||
floatingActionButton: controller.selectedEvents.isEmpty
|
||||
? (controller.showScrollDownButton
|
||||
// Pangea#
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(bottom: 56.0),
|
||||
child: FloatingActionButton(
|
||||
onPressed: controller.scrollDown,
|
||||
heroTag: null,
|
||||
mini: true,
|
||||
child: const Icon(Icons.arrow_downward_outlined),
|
||||
),
|
||||
)
|
||||
// #Pangea
|
||||
: controller.choreographer.errorService.error != null
|
||||
? ChoreographerHasErrorButton(
|
||||
controller.pangeaController,
|
||||
controller.choreographer.errorService.error!,
|
||||
)
|
||||
: controller.showPermissionsError
|
||||
? LanguagePermissionsButtons(
|
||||
choreographer: controller.choreographer,
|
||||
roomID: controller.roomId,
|
||||
)
|
||||
: null)
|
||||
// #Pangea
|
||||
: null,
|
||||
// ? Padding(
|
||||
// padding: const EdgeInsets.only(bottom: 56.0),
|
||||
// child: FloatingActionButton(
|
||||
// onPressed: controller.scrollDown,
|
||||
// heroTag: null,
|
||||
// mini: true,
|
||||
// child: const Icon(Icons.arrow_downward_outlined),
|
||||
// ),
|
||||
// )
|
||||
// : null,
|
||||
floatingActionButton: ChatFloatingActionButton(
|
||||
controller: controller,
|
||||
),
|
||||
// Pangea#
|
||||
body:
|
||||
// #Pangea
|
||||
// DropTarget(
|
||||
|
|
@ -338,120 +324,100 @@ class ChatView extends StatelessWidget {
|
|||
),
|
||||
if (controller.room.canSendDefaultMessages &&
|
||||
controller.room.membership == Membership.join)
|
||||
// #Pangea
|
||||
// Container(
|
||||
ConditionalFlexible(
|
||||
isScroll: controller.isRowScrollable,
|
||||
child: ConditionalScroll(
|
||||
isScroll: controller.isRowScrollable,
|
||||
child: MeasurableWidget(
|
||||
onChange: (size, position) {
|
||||
controller.inputRowSize = size!.height;
|
||||
},
|
||||
child: Container(
|
||||
// Pangea#
|
||||
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
|
||||
// ignore: deprecated_member_use
|
||||
.surfaceVariant,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
child: controller.room.isAbandonedDMRoom ==
|
||||
true
|
||||
? Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
// #Pangea
|
||||
if (controller.room.isRoomAdmin)
|
||||
TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
padding:
|
||||
const EdgeInsets.all(
|
||||
16,
|
||||
),
|
||||
foregroundColor:
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.error,
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.archive_outlined,
|
||||
),
|
||||
onPressed:
|
||||
controller.archiveChat,
|
||||
label: Text(
|
||||
L10n.of(context)!.archive,
|
||||
),
|
||||
),
|
||||
// Pangea#
|
||||
TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.all(
|
||||
16,
|
||||
),
|
||||
foregroundColor:
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.error,
|
||||
),
|
||||
icon: const Icon(
|
||||
// #Pangea
|
||||
// Icons.archive_outlined,
|
||||
Icons.arrow_forward,
|
||||
// Pangea#
|
||||
),
|
||||
onPressed: controller.leaveChat,
|
||||
label: Text(
|
||||
L10n.of(context)!.leave,
|
||||
),
|
||||
),
|
||||
TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.all(
|
||||
16,
|
||||
),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.forum_outlined,
|
||||
),
|
||||
onPressed:
|
||||
controller.recreateChat,
|
||||
label: Text(
|
||||
L10n.of(context)!.reopenChat,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const ConnectionStatusHeader(),
|
||||
ITBar(
|
||||
choreographer:
|
||||
controller.choreographer,
|
||||
),
|
||||
ReactionsPicker(controller),
|
||||
ReplyDisplay(controller),
|
||||
ChatInputRow(controller),
|
||||
ChatEmojiPicker(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
|
||||
// ignore: deprecated_member_use
|
||||
.surfaceVariant,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
child: controller.room.isAbandonedDMRoom == true
|
||||
? Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
// #Pangea
|
||||
if (controller.room.isRoomAdmin)
|
||||
TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.all(
|
||||
16,
|
||||
),
|
||||
foregroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.error,
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.archive_outlined,
|
||||
),
|
||||
onPressed: controller.archiveChat,
|
||||
label: Text(
|
||||
L10n.of(context)!.archive,
|
||||
),
|
||||
),
|
||||
// Pangea#
|
||||
TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.all(
|
||||
16,
|
||||
),
|
||||
foregroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.error,
|
||||
),
|
||||
icon: const Icon(
|
||||
// #Pangea
|
||||
// Icons.archive_outlined,
|
||||
Icons.arrow_forward,
|
||||
// Pangea#
|
||||
),
|
||||
onPressed: controller.leaveChat,
|
||||
label: Text(
|
||||
L10n.of(context)!.leave,
|
||||
),
|
||||
),
|
||||
TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.all(
|
||||
16,
|
||||
),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.forum_outlined,
|
||||
),
|
||||
onPressed: controller.recreateChat,
|
||||
label: Text(
|
||||
L10n.of(context)!.reopenChat,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const ConnectionStatusHeader(),
|
||||
ITBar(
|
||||
choreographer:
|
||||
controller.choreographer,
|
||||
),
|
||||
ReactionsPicker(controller),
|
||||
ReplyDisplay(controller),
|
||||
ChatInputRow(controller),
|
||||
ChatEmojiPicker(controller),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -484,35 +450,3 @@ class ChatView extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
// #Pangea
|
||||
Widget ConditionalFlexible({required bool isScroll, required Widget child}) {
|
||||
if (isScroll) {
|
||||
return Flexible(
|
||||
flex: 9999999,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
class ConditionalScroll extends StatelessWidget {
|
||||
final bool isScroll;
|
||||
final Widget child;
|
||||
const ConditionalScroll({
|
||||
super.key,
|
||||
required this.isScroll,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isScroll) {
|
||||
return SingleChildScrollView(
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
}
|
||||
// Pangea#
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ 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/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/message_buttons.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
|
||||
import 'package:fluffychat/utils/date_time_extension.dart';
|
||||
|
|
@ -39,7 +38,6 @@ class Message extends StatelessWidget {
|
|||
final bool animateIn;
|
||||
final void Function()? resetAnimateIn;
|
||||
// #Pangea
|
||||
final LanguageModel? selectedDisplayLang;
|
||||
final bool immersionMode;
|
||||
final bool definitions;
|
||||
final ChatController controller;
|
||||
|
|
@ -64,7 +62,6 @@ class Message extends StatelessWidget {
|
|||
this.resetAnimateIn,
|
||||
this.avatarPresenceBackgroundColor,
|
||||
// #Pangea
|
||||
required this.selectedDisplayLang,
|
||||
required this.immersionMode,
|
||||
required this.definitions,
|
||||
required this.controller,
|
||||
|
|
@ -82,9 +79,9 @@ class Message extends StatelessWidget {
|
|||
// #Pangea
|
||||
debugPrint('Message.build()');
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (controller.edittingEvents.contains(event.eventId)) {
|
||||
if (controller.pangeaEditingEvent?.eventId == event.eventId) {
|
||||
pangeaMessageEvent?.updateLatestEdit();
|
||||
controller.clearEdittingEvent(event.eventId);
|
||||
controller.clearEditingEvent();
|
||||
}
|
||||
});
|
||||
// Pangea#
|
||||
|
|
|
|||
|
|
@ -491,7 +491,12 @@ class InputBar extends StatelessWidget {
|
|||
textInputAction: textInputAction,
|
||||
autofocus: autofocus!,
|
||||
inputFormatters: [
|
||||
LengthLimitingTextInputFormatter((maxPDUSize / 3).floor()),
|
||||
//#Pangea
|
||||
//LengthLimitingTextInputFormatter((maxPDUSize / 3).floor()),
|
||||
//setting max character count to 1000
|
||||
//after max, nothing else can be typed
|
||||
LengthLimitingTextInputFormatter(1000),
|
||||
//Pangea#
|
||||
],
|
||||
onSubmitted: (text) {
|
||||
// fix for library for now
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_det
|
|||
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_invitation_buttons.dart';
|
||||
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_name_button.dart';
|
||||
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_capacity_button.dart';
|
||||
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_rules_editor.dart';
|
||||
import 'package:fluffychat/pangea/utils/lock_room.dart';
|
||||
import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart';
|
||||
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_settings.dart';
|
||||
|
|
@ -263,39 +262,20 @@ class ChatDetailsView extends StatelessWidget {
|
|||
controller: controller,
|
||||
),
|
||||
// Pangea#
|
||||
if (room.isSpace && room.isRoomAdmin)
|
||||
ListTile(
|
||||
title: Text(
|
||||
L10n.of(context)!.spaceAnalytics,
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
leading: CircleAvatar(
|
||||
backgroundColor:
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
foregroundColor: iconColor,
|
||||
child: const Icon(
|
||||
Icons.analytics_outlined,
|
||||
),
|
||||
),
|
||||
onTap: () => context.go(
|
||||
'/rooms/analytics/${room.id}',
|
||||
),
|
||||
),
|
||||
// commenting out language settings in spaces for now
|
||||
// if (room.languageSettings != null && room.isRoomAdmin)
|
||||
// LanguageSettings(
|
||||
// roomId: controller.roomId,
|
||||
// startOpen: false,
|
||||
// ),
|
||||
if (room.pangeaRoomRules != null)
|
||||
RoomRulesEditor(
|
||||
roomId: controller.roomId,
|
||||
startOpen: false,
|
||||
),
|
||||
|
||||
// Commenting out pangea room rules for now
|
||||
// if (room.pangeaRoomRules != null)
|
||||
// RoomRulesEditor(
|
||||
// roomId: controller.roomId,
|
||||
// startOpen: false,
|
||||
// ),
|
||||
|
||||
// if (!room.canChangeStateEvent(EventTypes.RoomTopic))
|
||||
// ListTile(
|
||||
// title: Text(
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import 'package:fluffychat/pangea/constants/pangea_room_types.dart';
|
|||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/utils/add_to_space.dart';
|
||||
import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
|
||||
|
|
@ -843,18 +842,12 @@ class ChatListController extends State<ChatList>
|
|||
if (firstSelectedRoom.isSpace && !space.isRoomAdmin) {
|
||||
throw L10n.of(context)!.cantAddSpaceChild;
|
||||
}
|
||||
await pangeaAddToSpace(
|
||||
space,
|
||||
selectedRoomIds.toList(),
|
||||
context,
|
||||
pangeaController,
|
||||
);
|
||||
|
||||
// if (space.canSendDefaultStates) {
|
||||
// for (final roomId in selectedRoomIds) {
|
||||
// await space.setSpaceChild(roomId);
|
||||
// }
|
||||
// }
|
||||
if (space.canSendDefaultStates) {
|
||||
for (final roomId in selectedRoomIds) {
|
||||
await space.pangeaSetSpaceChild(roomId);
|
||||
}
|
||||
}
|
||||
// Pangea#
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -137,7 +137,11 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||
]
|
||||
: selectMode == SelectMode.select
|
||||
? [
|
||||
if (controller.spaces.isNotEmpty)
|
||||
// #Pangea
|
||||
// if (controller.spaces.isNotEmpty)
|
||||
if (controller.spaces.isNotEmpty &&
|
||||
controller.selectedRoomIds.length == 1)
|
||||
// Pangea#
|
||||
IconButton(
|
||||
tooltip: L10n.of(context)!.addToSpace,
|
||||
icon: const Icon(Icons.workspaces_outlined),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:fluffychat/pangea/constants/class_default_values.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/utils/find_conversation_partner_dialog.dart';
|
||||
import 'package:fluffychat/pangea/utils/logout.dart';
|
||||
|
|
@ -53,21 +52,21 @@ class ClientChooserButton extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
enabled: matrix.client.rooms.any(
|
||||
(room) =>
|
||||
room.isSpace &&
|
||||
room.ownPowerLevel >= ClassDefaultValues.powerLevelOfAdmin,
|
||||
),
|
||||
value: SettingsAction.spaceAnalytics,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.analytics_outlined),
|
||||
const SizedBox(width: 18),
|
||||
Expanded(child: Text(L10n.of(context)!.spaceAnalytics)),
|
||||
],
|
||||
),
|
||||
),
|
||||
// PopupMenuItem(
|
||||
// enabled: matrix.client.rooms.any(
|
||||
// (room) =>
|
||||
// room.isSpace &&
|
||||
// room.ownPowerLevel >= ClassDefaultValues.powerLevelOfAdmin,
|
||||
// ),
|
||||
// value: SettingsAction.spaceAnalytics,
|
||||
// child: Row(
|
||||
// children: [
|
||||
// const Icon(Icons.analytics_outlined),
|
||||
// const SizedBox(width: 18),
|
||||
// Expanded(child: Text(L10n.of(context)!.spaceAnalytics)),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
PopupMenuItem(
|
||||
enabled: matrix.client.rooms.any(
|
||||
(room) => !room.isSpace && !room.isArchived && !room.isAnalyticsRoom,
|
||||
|
|
@ -101,17 +100,17 @@ class ClientChooserButton extends StatelessWidget {
|
|||
// ],
|
||||
// ),
|
||||
// ),
|
||||
if (controller.pangeaController.permissionsController.isUser18())
|
||||
PopupMenuItem(
|
||||
value: SettingsAction.findAConversationPartner,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.add_circle_outline),
|
||||
const SizedBox(width: 18),
|
||||
Expanded(child: Text(L10n.of(context)!.findALanguagePartner)),
|
||||
],
|
||||
),
|
||||
),
|
||||
// if (controller.pangeaController.permissionsController.isUser18())
|
||||
// PopupMenuItem(
|
||||
// value: SettingsAction.findAConversationPartner,
|
||||
// child: Row(
|
||||
// children: [
|
||||
// const Icon(Icons.add_circle_outline),
|
||||
// const SizedBox(width: 18),
|
||||
// Expanded(child: Text(L10n.of(context)!.findALanguagePartner)),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// PopupMenuItem(
|
||||
// value: SettingsAction.setStatus,
|
||||
// child: Row(
|
||||
|
|
@ -402,9 +401,9 @@ class ClientChooserButton extends StatelessWidget {
|
|||
controller.pangeaController,
|
||||
);
|
||||
break;
|
||||
case SettingsAction.spaceAnalytics:
|
||||
context.go('/rooms/analytics');
|
||||
break;
|
||||
// case SettingsAction.spaceAnalytics:
|
||||
// context.go('/rooms/analytics');
|
||||
// break;
|
||||
case SettingsAction.myAnalytics:
|
||||
context.go('/rooms/mylearning');
|
||||
break;
|
||||
|
|
@ -497,7 +496,7 @@ enum SettingsAction {
|
|||
// #Pangea
|
||||
learning,
|
||||
joinWithClassCode,
|
||||
spaceAnalytics,
|
||||
// spaceAnalytics,
|
||||
myAnalytics,
|
||||
findAConversationPartner,
|
||||
logout,
|
||||
|
|
|
|||
|
|
@ -50,10 +50,7 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
|
||||
final String _chatCountsKey = 'chatCounts';
|
||||
Map<String, int> get chatCounts => Map.from(
|
||||
widget.controller.pangeaController.pStoreService.read(
|
||||
_chatCountsKey,
|
||||
local: true,
|
||||
) ??
|
||||
widget.controller.pangeaController.pStoreService.read(_chatCountsKey) ??
|
||||
{},
|
||||
);
|
||||
// Pangea#
|
||||
|
|
@ -128,24 +125,35 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
activeSpaceId,
|
||||
maxDepth: 1,
|
||||
from: prevBatch,
|
||||
// #Pangea
|
||||
limit: 100,
|
||||
// Pangea#
|
||||
);
|
||||
|
||||
if (prevBatch != null) {
|
||||
response.rooms.insertAll(0, _lastResponse[activeSpaceId]?.rooms ?? []);
|
||||
}
|
||||
setState(() {
|
||||
_lastResponse[activeSpaceId] = response;
|
||||
});
|
||||
// #Pangea
|
||||
if (mounted) {
|
||||
// Pangea#
|
||||
setState(() {
|
||||
_lastResponse[activeSpaceId] = response;
|
||||
});
|
||||
}
|
||||
return _lastResponse[activeSpaceId]!;
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
error = e;
|
||||
});
|
||||
// #Pangea
|
||||
if (mounted) {
|
||||
// Pangea#
|
||||
setState(() {
|
||||
error = e;
|
||||
});
|
||||
}
|
||||
rethrow;
|
||||
} finally {
|
||||
// #Pangea
|
||||
if (activeSpace != null) {
|
||||
await setChatCount(
|
||||
setChatCount(
|
||||
activeSpace,
|
||||
_lastResponse[activeSpaceId] ??
|
||||
GetSpaceHierarchyResponse(
|
||||
|
|
@ -153,10 +161,12 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
),
|
||||
);
|
||||
}
|
||||
// Pangea#
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
if (mounted) {
|
||||
// Pangea#
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -454,9 +464,22 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
|
||||
// #Pangea
|
||||
Future<void> loadChatCounts() async {
|
||||
for (final Room room in Matrix.of(context).client.rooms) {
|
||||
if (room.isSpace && !chatCounts.containsKey(room.id)) {
|
||||
await loadHierarchy(null, room.id);
|
||||
// if not in the call spaces view, don't load chat count yet
|
||||
if (widget.controller.activeSpaceId != null) return;
|
||||
|
||||
final List<Room> allSpaces =
|
||||
Matrix.of(context).client.rooms.where((room) => room.isSpace).toList();
|
||||
|
||||
for (final Room space in allSpaces) {
|
||||
// check if the space is visible in the all spaces list
|
||||
final bool isRootSpace = !allSpaces.any(
|
||||
(parentSpace) =>
|
||||
parentSpace.spaceChildren.any((child) => child.roomId == space.id),
|
||||
);
|
||||
|
||||
// if it's visible, and it hasn't been loaded yet, load chat count
|
||||
if (isRootSpace && !chatCounts.containsKey(space.id)) {
|
||||
await loadHierarchy(null, space.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -482,12 +505,14 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
event.isSpaceChildUpdate(
|
||||
widget.controller.activeSpaceId!,
|
||||
)) {
|
||||
debugPrint("refresh on update");
|
||||
await loadHierarchy();
|
||||
}
|
||||
setState(() => refreshing = false);
|
||||
}
|
||||
|
||||
bool includeSpaceChild(sc, matchingSpaceChildren) {
|
||||
if (!mounted) return false;
|
||||
final bool isAnalyticsRoom = sc.roomType == PangeaRoomTypes.analytics;
|
||||
final bool isMember = [Membership.join, Membership.invite]
|
||||
.contains(Matrix.of(context).client.getRoomById(sc.roomId)?.membership);
|
||||
|
|
@ -550,7 +575,6 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
await widget.controller.pangeaController.pStoreService.save(
|
||||
_chatCountsKey,
|
||||
updatedChatCounts,
|
||||
local: true,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -856,24 +880,22 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
),
|
||||
// Pangea#
|
||||
),
|
||||
// #Pangea
|
||||
// if (activeSpace?.canChangeStateEvent(
|
||||
// EventTypes.SpaceChild,
|
||||
// ) ==
|
||||
// true)
|
||||
// Material(
|
||||
// child: ListTile(
|
||||
// leading: const CircleAvatar(
|
||||
// child: Icon(Icons.group_add_outlined),
|
||||
// ),
|
||||
// title:
|
||||
// Text(L10n.of(context)!.addChatOrSubSpace),
|
||||
// trailing:
|
||||
// const Icon(Icons.chevron_right_outlined),
|
||||
// onTap: _addChatOrSubSpace,
|
||||
// ),
|
||||
// ),
|
||||
// Pangea#
|
||||
if (activeSpace?.canChangeStateEvent(
|
||||
EventTypes.SpaceChild,
|
||||
) ==
|
||||
true)
|
||||
Material(
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
child: Icon(Icons.group_add_outlined),
|
||||
),
|
||||
title:
|
||||
Text(L10n.of(context)!.addChatOrSubSpace),
|
||||
trailing:
|
||||
const Icon(Icons.chevron_right_outlined),
|
||||
onTap: _addChatOrSubSpace,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
|
||||
class PermissionsListTile extends StatelessWidget {
|
||||
final String permissionKey;
|
||||
final int permission;
|
||||
|
|
@ -72,7 +72,16 @@ class PermissionsListTile extends StatelessWidget {
|
|||
return ListTile(
|
||||
title: Text(getLocalizedPowerLevelString(context)),
|
||||
subtitle: Text(
|
||||
L10n.of(context)!.minimumPowerLevel(permission.toString()),
|
||||
// #Pangea
|
||||
// L10n.of(context)!.minimumPowerLevel(permission.toString()),
|
||||
L10n.of(context)!.minimumPowerLevel(
|
||||
Matrix.of(context).client.powerLevelName(
|
||||
permission,
|
||||
L10n.of(context)!,
|
||||
) ??
|
||||
permission.toString(),
|
||||
),
|
||||
// Pangea#
|
||||
),
|
||||
trailing: Material(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fluffychat/pages/invitation_selection/invitation_selection_view.dart';
|
||||
import 'package:fluffychat/pangea/constants/class_default_values.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||
|
|
@ -48,16 +49,29 @@ class InvitationSelectionController extends State<InvitationSelection> {
|
|||
);
|
||||
final contacts = client.rooms
|
||||
.where((r) => r.isDirectChat)
|
||||
.map((r) => r.unsafeGetUserFromMemoryOrFallback(r.directChatMatrixID!))
|
||||
// #Pangea
|
||||
// .map((r) => r.unsafeGetUserFromMemoryOrFallback(r.directChatMatrixID!))
|
||||
.map(
|
||||
(r) => r
|
||||
.getParticipants()
|
||||
.firstWhereOrNull((u) => u.id != client.userID),
|
||||
)
|
||||
// Pangea#
|
||||
.toList();
|
||||
// #Pangea
|
||||
contacts.removeWhere((u) => u == null || u.id != BotName.byEnvironment);
|
||||
contacts.sort(
|
||||
(a, b) => a.calcDisplayname().toLowerCase().compareTo(
|
||||
b.calcDisplayname().toLowerCase(),
|
||||
(a, b) => a!.calcDisplayname().toLowerCase().compareTo(
|
||||
b!.calcDisplayname().toLowerCase(),
|
||||
),
|
||||
);
|
||||
//#Pangea
|
||||
return contacts.cast<User>();
|
||||
// contacts.sort(
|
||||
// (a, b) => a.calcDisplayname().toLowerCase().compareTo(
|
||||
// b.calcDisplayname().toLowerCase(),
|
||||
// ),
|
||||
// );
|
||||
// return contacts;
|
||||
return contacts.where((u) => u.id != BotName.byEnvironment).toList();
|
||||
//Pangea#
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ class LoginView extends StatelessWidget {
|
|||
controller.showPassword
|
||||
? Icons.visibility_off_outlined
|
||||
: Icons.visibility_outlined,
|
||||
color: Colors.black,
|
||||
// color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ class NewGroupController extends State<NewGroup> {
|
|||
powerLevelContentOverride:
|
||||
await ClassChatPowerLevels.powerLevelOverrideForClassChat(
|
||||
context,
|
||||
addToSpaceKey.currentState!.parents,
|
||||
addToSpaceKey.currentState!.parent,
|
||||
),
|
||||
invite: [
|
||||
if (addConversationBotKey.currentState?.addBot ?? false)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,15 @@ class NewGroupView extends StatelessWidget {
|
|||
),
|
||||
title: Text(L10n.of(context)!.createGroup),
|
||||
),
|
||||
// #Pangea
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: controller.loading ? null : controller.submitAction,
|
||||
icon: controller.loading ? null : const Icon(Icons.chat_bubble_outline),
|
||||
label: controller.loading
|
||||
? const CircularProgressIndicator.adaptive()
|
||||
: Text(L10n.of(context)!.createChat),
|
||||
),
|
||||
// Pangea#
|
||||
body: MaxWidthBody(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
|
@ -134,33 +143,33 @@ class NewGroupView extends StatelessWidget {
|
|||
// value: !controller.publicGroup,
|
||||
// onChanged: null,
|
||||
// ),
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.all(16.0),
|
||||
// child: SizedBox(
|
||||
// width: double.infinity,
|
||||
// child: ElevatedButton(
|
||||
// style: ElevatedButton.styleFrom(
|
||||
// foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
// ),
|
||||
// onPressed:
|
||||
// controller.loading ? null : controller.submitAction,
|
||||
// child: controller.loading
|
||||
// ? const LinearProgressIndicator()
|
||||
// : Row(
|
||||
// children: [
|
||||
// Expanded(
|
||||
// child: Text(
|
||||
// L10n.of(context)!.createGroupAndInviteUsers,
|
||||
// ),
|
||||
// ),
|
||||
// Icon(Icons.adaptive.arrow_forward_outlined),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Pangea#
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed:
|
||||
controller.loading ? null : controller.submitAction,
|
||||
child: controller.loading
|
||||
? const LinearProgressIndicator()
|
||||
: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
L10n.of(context)!.createGroupAndInviteUsers,
|
||||
),
|
||||
),
|
||||
Icon(Icons.adaptive.arrow_forward_outlined),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: error == null
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ class NewSpaceController extends State<NewSpace> {
|
|||
powerLevelContentOverride: addToSpaceKey.currentState != null
|
||||
? await ClassChatPowerLevels.powerLevelOverrideForClassChat(
|
||||
context,
|
||||
addToSpaceKey.currentState!.parents,
|
||||
addToSpaceKey.currentState!.parent,
|
||||
)
|
||||
: null,
|
||||
// initialState: [
|
||||
|
|
@ -198,14 +198,6 @@ class NewSpaceController extends State<NewSpace> {
|
|||
if (capacity != null && space != null) {
|
||||
space.updateRoomCapacity(capacity);
|
||||
}
|
||||
final newChatRoomId = await Matrix.of(context).client.createGroupChat(
|
||||
enableEncryption: false,
|
||||
preset: sdk.CreateRoomPreset.publicChat,
|
||||
// Welcome chat name is '[space name acronym]: Welcome Chat'
|
||||
groupName:
|
||||
'${nameController.text.trim().split(RegExp(r"\s+")).map((s) => s[0]).join()}: ${L10n.of(context)!.classWelcomeChat}',
|
||||
);
|
||||
GoogleAnalytics.createChat(newChatRoomId);
|
||||
|
||||
final Room? room = Matrix.of(context).client.getRoomById(spaceId);
|
||||
if (room == null) {
|
||||
|
|
@ -217,12 +209,6 @@ class NewSpaceController extends State<NewSpace> {
|
|||
return;
|
||||
}
|
||||
|
||||
room.setSpaceChild(newChatRoomId, suggested: true);
|
||||
GoogleAnalytics.addParent(
|
||||
newChatRoomId,
|
||||
room.classCode,
|
||||
);
|
||||
|
||||
GoogleAnalytics.createClass(room.name, room.classCode);
|
||||
try {
|
||||
await room.invite(BotName.byEnvironment);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart';
|
||||
import 'package:fluffychat/pangea/models/space_model.dart';
|
||||
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_capacity_button.dart';
|
||||
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_rules_editor.dart';
|
||||
import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
|
|
@ -32,6 +28,15 @@ class NewSpaceView extends StatelessWidget {
|
|||
// Pangea#
|
||||
title: Text(L10n.of(context)!.createNewSpace),
|
||||
),
|
||||
// #Pangea
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: controller.loading ? null : controller.submitAction,
|
||||
icon: controller.loading ? null : const Icon(Icons.workspaces_outlined),
|
||||
label: controller.loading
|
||||
? const CircularProgressIndicator.adaptive()
|
||||
: Text(L10n.of(context)!.createSpace),
|
||||
),
|
||||
// Pangea#
|
||||
body: MaxWidthBody(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
|
@ -124,67 +129,71 @@ class NewSpaceView extends StatelessWidget {
|
|||
startOpen: true,
|
||||
spaceMode: true,
|
||||
),
|
||||
if (controller.rulesEditorKey.currentState != null)
|
||||
RoomRulesEditor(
|
||||
key: controller.rulesEditorKey,
|
||||
roomId: null,
|
||||
startOpen: false,
|
||||
initialRules: controller.rulesEditorKey.currentState!.rules,
|
||||
),
|
||||
if (controller.rulesEditorKey.currentState == null)
|
||||
FutureBuilder<PangeaRoomRules?>(
|
||||
future: Matrix.of(context).client.lastUpdatedRoomRules,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
return RoomRulesEditor(
|
||||
key: controller.rulesEditorKey,
|
||||
roomId: null,
|
||||
startOpen: false,
|
||||
initialRules: snapshot.data,
|
||||
);
|
||||
} else {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Center(
|
||||
child:
|
||||
CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
// Commenting out pangea room rules for now
|
||||
// if (controller.rulesEditorKey.currentState != null)
|
||||
// RoomRulesEditor(
|
||||
// key: controller.rulesEditorKey,
|
||||
// roomId: null,
|
||||
// startOpen: false,
|
||||
// initialRules: controller.rulesEditorKey.currentState!.rules,
|
||||
// ),
|
||||
|
||||
// Commenting out pangea room rules for now
|
||||
// if (controller.rulesEditorKey.currentState == null)
|
||||
// FutureBuilder<PangeaRoomRules?>(
|
||||
// future: Matrix.of(context).client.lastUpdatedRoomRules,
|
||||
// builder: (context, snapshot) {
|
||||
// if (snapshot.connectionState == ConnectionState.done) {
|
||||
// return RoomRulesEditor(
|
||||
// key: controller.rulesEditorKey,
|
||||
// roomId: null,
|
||||
// startOpen: false,
|
||||
// initialRules: snapshot.data,
|
||||
// );
|
||||
// } else {
|
||||
// return const Padding(
|
||||
// padding: EdgeInsets.all(16.0),
|
||||
// child: Center(
|
||||
// child:
|
||||
// CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
|
||||
// SwitchListTile.adaptive(
|
||||
// title: Text(L10n.of(context)!.spaceIsPublic),
|
||||
// value: controller.publicGroup,
|
||||
// onChanged: controller.setPublicGroup,
|
||||
// ),
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.all(16.0),
|
||||
// child: SizedBox(
|
||||
// width: double.infinity,
|
||||
// child: ElevatedButton(
|
||||
// style: ElevatedButton.styleFrom(
|
||||
// foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
// ),
|
||||
// onPressed:
|
||||
// controller.loading ? null : controller.submitAction,
|
||||
// child: controller.loading
|
||||
// ? const LinearProgressIndicator()
|
||||
// : Row(
|
||||
// children: [
|
||||
// Expanded(
|
||||
// child: Text(
|
||||
// L10n.of(context)!.createNewSpace,
|
||||
// ),
|
||||
// ),
|
||||
// Icon(Icons.adaptive.arrow_forward_outlined),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Pangea#
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed:
|
||||
controller.loading ? null : controller.submitAction,
|
||||
child: controller.loading
|
||||
? const LinearProgressIndicator()
|
||||
: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
L10n.of(context)!.createNewSpace,
|
||||
),
|
||||
),
|
||||
Icon(Icons.adaptive.arrow_forward_outlined),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart'; //adding to check app version
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'settings.dart';
|
||||
|
|
@ -17,6 +18,14 @@ class SettingsView extends StatelessWidget {
|
|||
|
||||
const SettingsView(this.controller, {super.key});
|
||||
|
||||
// #Pangea
|
||||
Future<String> getAppVersion(BuildContext context) async {
|
||||
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
return L10n.of(context)!
|
||||
.versionText(packageInfo.version, packageInfo.buildNumber);
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// #Pangea
|
||||
|
|
@ -251,6 +260,30 @@ class SettingsView extends StatelessWidget {
|
|||
onTap: () => launchUrlString(AppConfig.termsOfServiceUrl),
|
||||
trailing: const Icon(Icons.open_in_new_outlined),
|
||||
),
|
||||
FutureBuilder<String>(
|
||||
future: getAppVersion(context),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.info_outline),
|
||||
title: Text(
|
||||
snapshot.data ?? L10n.of(context)!.versionNotFound,
|
||||
),
|
||||
);
|
||||
} else if (snapshot.hasError) {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.error_outline),
|
||||
title: Text(L10n.of(context)!.versionFetchError),
|
||||
);
|
||||
} else {
|
||||
return ListTile(
|
||||
leading: const CircularProgressIndicator(),
|
||||
title: Text(L10n.of(context)!.fetchingVersion),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
// Conditional ListTile based on the environment (staging or not)
|
||||
if (Environment.isStaging)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.bug_report_outlined),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:fluffychat/widgets/app_lock.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'package:fluffychat/widgets/app_lock.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../bootstrap/bootstrap_dialog.dart';
|
||||
import 'settings_security_view.dart';
|
||||
|
||||
|
|
@ -51,6 +51,28 @@ class SettingsSecurityController extends State<SettingsSecurity> {
|
|||
}
|
||||
|
||||
void deleteAccountAction() async {
|
||||
// #Pangea
|
||||
final subscriptionController =
|
||||
MatrixState.pangeaController.subscriptionController;
|
||||
if (subscriptionController.subscription?.isPaidSubscription == true &&
|
||||
subscriptionController.subscription?.defaultManagementURL != null) {
|
||||
final resp = await showOkCancelAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
title: L10n.of(context)!.deleteSubscriptionWarningTitle,
|
||||
message: L10n.of(context)!.deleteSubscriptionWarningBody,
|
||||
okLabel: L10n.of(context)!.manageSubscription,
|
||||
cancelLabel: L10n.of(context)!.continueText,
|
||||
);
|
||||
if (resp == OkCancelResult.ok) {
|
||||
launchUrlString(
|
||||
subscriptionController.subscription!.defaultManagementURL!,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Pangea#
|
||||
if (await showOkCancelAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/utils/bot_name.dart';
|
||||
import 'package:fluffychat/utils/date_time_extension.dart';
|
||||
import 'package:fluffychat/utils/fluffy_share.dart';
|
||||
import 'package:fluffychat/utils/url_launcher.dart';
|
||||
|
|
@ -91,7 +92,12 @@ class UserBottomSheetView extends StatelessWidget {
|
|||
),
|
||||
actions: [
|
||||
if (userId != client.userID &&
|
||||
!client.ignoredUsers.contains(userId))
|
||||
!client.ignoredUsers.contains(userId)
|
||||
// #Pangea
|
||||
&&
|
||||
userId != BotName.byEnvironment
|
||||
// Pangea#
|
||||
)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: IconButton(
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart';
|
||||
import 'package:fluffychat/pangea/repo/full_text_translation_repo.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../../repo/similarity_repo.dart';
|
||||
|
||||
class AlternativeTranslator {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import 'dart:developer';
|
|||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/alternative_translator.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/igc_controller.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/message_options.dart';
|
||||
import 'package:fluffychat/pangea/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
|
||||
|
|
@ -14,7 +13,6 @@ import 'package:fluffychat/pangea/models/it_step.dart';
|
|||
import 'package:fluffychat/pangea/models/representation_content_model.dart';
|
||||
import 'package:fluffychat/pangea/models/space_model.dart';
|
||||
import 'package:fluffychat/pangea/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/models/user_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/any_state_holder.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/utils/overlay.dart';
|
||||
|
|
@ -39,7 +37,6 @@ class Choreographer {
|
|||
late PangeaTextController _textController;
|
||||
late ITController itController;
|
||||
late IgcController igc;
|
||||
late MessageOptions messageOptions;
|
||||
late AlternativeTranslator altTranslator;
|
||||
late ErrorService errorService;
|
||||
|
||||
|
|
@ -60,7 +57,6 @@ class Choreographer {
|
|||
_textController = PangeaTextController(choreographer: this);
|
||||
itController = ITController(this);
|
||||
igc = IgcController(this);
|
||||
messageOptions = MessageOptions(this);
|
||||
errorService = ErrorService(this);
|
||||
altTranslator = AlternativeTranslator(this);
|
||||
_textController.addListener(_onChangeListener);
|
||||
|
|
@ -180,18 +176,9 @@ class Choreographer {
|
|||
return;
|
||||
}
|
||||
|
||||
if ([
|
||||
EditType.igc,
|
||||
].contains(_textController.editType)) {
|
||||
// this may be unnecessary now that tokens are not used
|
||||
// to allow click of words in the input field and we're getting this at the end
|
||||
// TODO - turn it off and tested that this is fine
|
||||
igc.justGetTokensAndAddThemToIGCTextData();
|
||||
|
||||
// we set editType to keyboard here because that is the default for it
|
||||
// and we want to make sure that the next change is treated as a keyboard change
|
||||
// unless the system explicity sets it to something else. this
|
||||
textController.editType = EditType.keyboard;
|
||||
if (_textController.editType == EditType.igc) {
|
||||
_lastChecked = _textController.text;
|
||||
_textController.editType = EditType.keyboard;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -523,11 +510,9 @@ class Choreographer {
|
|||
chatController.room,
|
||||
);
|
||||
|
||||
bool get itAutoPlayEnabled =>
|
||||
pangeaController.pStoreService.read(
|
||||
MatrixProfile.itAutoPlay.title,
|
||||
) ??
|
||||
false;
|
||||
bool get itAutoPlayEnabled {
|
||||
return pangeaController.userController.profile.userSettings.itAutoPlay;
|
||||
}
|
||||
|
||||
bool get definitionsEnabled =>
|
||||
pangeaController.permissionsController.isToolEnabled(
|
||||
|
|
|
|||
|
|
@ -7,11 +7,9 @@ import 'package:fluffychat/pangea/choreographer/controllers/span_data_controller
|
|||
import 'package:fluffychat/pangea/models/igc_text_data_model.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_match_model.dart';
|
||||
import 'package:fluffychat/pangea/repo/igc_repo.dart';
|
||||
import 'package:fluffychat/pangea/repo/tokens_repo.dart';
|
||||
import 'package:fluffychat/pangea/widgets/igc/span_card.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import '../../models/span_card_model.dart';
|
||||
import '../../utils/error_handler.dart';
|
||||
|
|
@ -83,62 +81,6 @@ class IgcController {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> justGetTokensAndAddThemToIGCTextData() async {
|
||||
try {
|
||||
if (igcTextData == null) {
|
||||
debugger(when: kDebugMode);
|
||||
choreographer.getLanguageHelp();
|
||||
return;
|
||||
}
|
||||
igcTextData!.loading = true;
|
||||
choreographer.startLoading();
|
||||
if (igcTextData!.originalInput != choreographer.textController.text) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
m: "igcTextData fullText does not match current text",
|
||||
s: StackTrace.current,
|
||||
data: igcTextData!.toJson(),
|
||||
);
|
||||
}
|
||||
|
||||
if (choreographer.l1LangCode == null ||
|
||||
choreographer.l2LangCode == null) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
m: "l1LangCode and/or l2LangCode is null",
|
||||
s: StackTrace.current,
|
||||
data: {
|
||||
"l1LangCode": choreographer.l1LangCode,
|
||||
"l2LangCode": choreographer.l2LangCode,
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final TokensResponseModel res = await TokensRepo.tokenize(
|
||||
await choreographer.pangeaController.userController.accessToken,
|
||||
TokensRequestModel(
|
||||
fullText: igcTextData!.originalInput,
|
||||
userL1: choreographer.l1LangCode!,
|
||||
userL2: choreographer.l2LangCode!,
|
||||
),
|
||||
);
|
||||
igcTextData?.tokens = res.tokens;
|
||||
} catch (err, stack) {
|
||||
debugger(when: kDebugMode);
|
||||
choreographer.errorService.setError(
|
||||
ChoreoError(type: ChoreoErrorType.unknown, raw: err),
|
||||
);
|
||||
Sentry.addBreadcrumb(
|
||||
Breadcrumb.fromJson({"igctextDdata": igcTextData?.toJson()}),
|
||||
);
|
||||
ErrorHandler.logError(e: err, s: stack);
|
||||
} finally {
|
||||
igcTextData?.loading = false;
|
||||
choreographer.stopLoading();
|
||||
}
|
||||
}
|
||||
|
||||
void showFirstMatch(BuildContext context) {
|
||||
if (igcTextData == null || igcTextData!.matches.isEmpty) {
|
||||
debugger(when: kDebugMode);
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
||||
import 'package:fluffychat/pangea/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
|
||||
|
||||
class MessageOptions {
|
||||
Choreographer choreographer;
|
||||
LanguageModel? _selectedDisplayLang;
|
||||
|
||||
MessageOptions(this.choreographer);
|
||||
|
||||
LanguageModel? get selectedDisplayLang {
|
||||
if (_selectedDisplayLang != null &&
|
||||
_selectedDisplayLang!.langCode != LanguageKeys.unknownLanguage) {
|
||||
return _selectedDisplayLang;
|
||||
}
|
||||
_selectedDisplayLang = choreographer.l2Lang;
|
||||
return _selectedDisplayLang;
|
||||
}
|
||||
|
||||
bool get isTranslationOn =>
|
||||
_selectedDisplayLang?.langCode != choreographer.l2LangCode;
|
||||
|
||||
// void setSelectedDisplayLang(LanguageModel? newLang) {
|
||||
// _selectedDisplayLang = newLang;
|
||||
// choreographer.setState();
|
||||
// }
|
||||
|
||||
void toggleSelectedDisplayLang() {
|
||||
if (_selectedDisplayLang?.langCode == choreographer.l2LangCode) {
|
||||
_selectedDisplayLang = choreographer.l1Lang;
|
||||
} else {
|
||||
_selectedDisplayLang = choreographer.l2Lang;
|
||||
}
|
||||
debugPrint('toggleSelectedDisplayLang: ${_selectedDisplayLang?.langCode}');
|
||||
choreographer.setState();
|
||||
GoogleAnalytics.messageTranslate();
|
||||
}
|
||||
|
||||
void resetSelectedDisplayLang() {
|
||||
_selectedDisplayLang = choreographer.l2Lang;
|
||||
choreographer.setState();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
||||
|
|
@ -7,7 +8,6 @@ import 'package:fluffychat/pangea/choreographer/widgets/it_feedback_card.dart';
|
|||
import 'package:fluffychat/pangea/choreographer/widgets/translation_finished_flow.dart';
|
||||
import 'package:fluffychat/pangea/constants/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
|
@ -18,112 +18,140 @@ import '../../utils/overlay.dart';
|
|||
import '../../widgets/igc/word_data_card.dart';
|
||||
import 'choice_array.dart';
|
||||
|
||||
class ITBar extends StatelessWidget {
|
||||
class ITBar extends StatefulWidget {
|
||||
final Choreographer choreographer;
|
||||
const ITBar({super.key, required this.choreographer});
|
||||
|
||||
ITController get itController => choreographer.itController;
|
||||
@override
|
||||
ITBarState createState() => ITBarState();
|
||||
}
|
||||
|
||||
class ITBarState extends State<ITBar> {
|
||||
ITController get itController => widget.choreographer.itController;
|
||||
StreamSubscription? _choreoSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
// Rebuild the widget each time there's an update from choreo.
|
||||
_choreoSub = widget.choreographer.stateListener.stream.listen((_) {
|
||||
setState(() {});
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_choreoSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return AnimatedSize(
|
||||
duration: itController.willOpen
|
||||
? const Duration(milliseconds: 2000)
|
||||
: const Duration(milliseconds: 500),
|
||||
? const Duration(milliseconds: 2000)
|
||||
: const Duration(milliseconds: 500),
|
||||
curve: Curves.fastOutSlowIn,
|
||||
clipBehavior: Clip.none,
|
||||
child: !itController.willOpen
|
||||
? const SizedBox()
|
||||
: CompositedTransformTarget(
|
||||
link: choreographer.itBarLinkAndKey.link,
|
||||
child: AnimatedOpacity(
|
||||
duration: itController.willOpen
|
||||
? const Duration(milliseconds: 2000)
|
||||
: const Duration(milliseconds: 500),
|
||||
opacity: itController.willOpen ? 1.0 : 0.0,
|
||||
child: Container(
|
||||
key: choreographer.itBarLinkAndKey.key,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).brightness == Brightness.light
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(AppConfig.borderRadius),
|
||||
topRight: Radius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
),
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(0, 3, 3, 3),
|
||||
child: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
// Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// // Row(
|
||||
// // mainAxisAlignment: MainAxisAlignment.start,
|
||||
// // crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// // children: [
|
||||
// // CounterDisplay(
|
||||
// // correct: controller.correctChoices,
|
||||
// // custom: controller.customChoices,
|
||||
// // incorrect: controller.incorrectChoices,
|
||||
// // yellow: controller.wildcardChoices,
|
||||
// // ),
|
||||
// // CompositedTransformTarget(
|
||||
// // link: choreographer.itBotLayerLinkAndKey.link,
|
||||
// // child: ITBotButton(
|
||||
// // key: choreographer.itBotLayerLinkAndKey.key,
|
||||
// // choreographer: choreographer,
|
||||
// // ),
|
||||
// // ),
|
||||
// // ],
|
||||
// // ),
|
||||
// ITCloseButton(choreographer: choreographer),
|
||||
// ],
|
||||
// ),
|
||||
// const SizedBox(height: 40.0),
|
||||
OriginalText(controller: itController),
|
||||
const SizedBox(height: 7.0),
|
||||
IntrinsicHeight(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(minHeight: 80),
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: Center(
|
||||
child: itController.choreographer.errorService.isError
|
||||
? ITError(
|
||||
error: itController
|
||||
.choreographer.errorService.error!,
|
||||
controller: itController,
|
||||
)
|
||||
: itController.showChoiceFeedback
|
||||
? ChoiceFeedbackText(controller: itController)
|
||||
: itController.isTranslationDone
|
||||
? TranslationFeedback(
|
||||
controller: itController,
|
||||
)
|
||||
: ITChoices(controller: itController),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
? const SizedBox()
|
||||
: CompositedTransformTarget(
|
||||
link: widget.choreographer.itBarLinkAndKey.link,
|
||||
child: AnimatedOpacity(
|
||||
duration: itController.willOpen
|
||||
? const Duration(milliseconds: 2000)
|
||||
: const Duration(milliseconds: 500),
|
||||
opacity: itController.willOpen ? 1.0 : 0.0,
|
||||
child: Container(
|
||||
key: widget.choreographer.itBarLinkAndKey.key,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).brightness == Brightness.light
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(AppConfig.borderRadius),
|
||||
topRight: Radius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 0.0,
|
||||
right: 0.0,
|
||||
child: ITCloseButton(choreographer: choreographer),
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(0, 3, 3, 3),
|
||||
child: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
// Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// // Row(
|
||||
// // mainAxisAlignment: MainAxisAlignment.start,
|
||||
// // crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// // children: [
|
||||
// // CounterDisplay(
|
||||
// // correct: controller.correctChoices,
|
||||
// // custom: controller.customChoices,
|
||||
// // incorrect: controller.incorrectChoices,
|
||||
// // yellow: controller.wildcardChoices,
|
||||
// // ),
|
||||
// // CompositedTransformTarget(
|
||||
// // link: choreographer.itBotLayerLinkAndKey.link,
|
||||
// // child: ITBotButton(
|
||||
// // key: choreographer.itBotLayerLinkAndKey.key,
|
||||
// // choreographer: choreographer,
|
||||
// // ),
|
||||
// // ),
|
||||
// // ],
|
||||
// // ),
|
||||
// ITCloseButton(choreographer: choreographer),
|
||||
// ],
|
||||
// ),
|
||||
// const SizedBox(height: 40.0),
|
||||
OriginalText(controller: itController),
|
||||
const SizedBox(height: 7.0),
|
||||
IntrinsicHeight(
|
||||
child: Container(
|
||||
constraints:
|
||||
const BoxConstraints(minHeight: 80),
|
||||
width: double.infinity,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: Center(
|
||||
child: itController
|
||||
.choreographer.errorService.isError
|
||||
? ITError(
|
||||
error: itController.choreographer
|
||||
.errorService.error!,
|
||||
controller: itController,
|
||||
)
|
||||
: itController.showChoiceFeedback
|
||||
? ChoiceFeedbackText(
|
||||
controller: itController,
|
||||
)
|
||||
: itController.isTranslationDone
|
||||
? TranslationFeedback(
|
||||
controller: itController,
|
||||
)
|
||||
: ITChoices(
|
||||
controller: itController,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 0.0,
|
||||
right: 0.0,
|
||||
child:
|
||||
ITCloseButton(choreographer: widget.choreographer),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -199,20 +227,16 @@ class OriginalText extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (
|
||||
!controller.isEditingSourceText
|
||||
&& controller.sourceText != null
|
||||
)
|
||||
if (!controller.isEditingSourceText && controller.sourceText != null)
|
||||
AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
opacity: controller.nextITStep != null
|
||||
? 1.0
|
||||
: 0.0,
|
||||
opacity: controller.nextITStep != null ? 1.0 : 0.0,
|
||||
child: IconButton(
|
||||
onPressed: () => {
|
||||
if (controller.nextITStep != null) {
|
||||
controller.setIsEditingSourceText(true),
|
||||
},
|
||||
if (controller.nextITStep != null)
|
||||
{
|
||||
controller.setIsEditingSourceText(true),
|
||||
},
|
||||
},
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
),
|
||||
|
|
@ -309,9 +333,9 @@ class ITChoices extends StatelessWidget {
|
|||
choices: controller.currentITStep!.continuances.map((e) {
|
||||
try {
|
||||
return Choice(
|
||||
text: e.text.trim(),
|
||||
color: e.color,
|
||||
isGold: e.description == "best",
|
||||
text: e.text.trim(),
|
||||
color: e.color,
|
||||
isGold: e.description == "best",
|
||||
);
|
||||
} catch (e) {
|
||||
debugger(when: kDebugMode);
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ class TranslateButton extends StatelessWidget {
|
|||
return TextButton(
|
||||
onPressed: loading ? null : onPress,
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all<Color>(
|
||||
backgroundColor: WidgetStateProperty.all<Color>(
|
||||
AppConfig.primaryColor.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import '../../../config/app_config.dart';
|
||||
import '../../../pages/chat/chat.dart';
|
||||
|
||||
class LanguageDisplayToggle extends StatelessWidget {
|
||||
const LanguageDisplayToggle({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
final ChatController controller;
|
||||
|
||||
get onPressed =>
|
||||
controller.choreographer.messageOptions.toggleSelectedDisplayLang;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// if (!controller.choreographer.translationEnabled) {
|
||||
// return const SizedBox();
|
||||
// }
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: controller.choreographer.messageOptions.isTranslationOn
|
||||
? AppConfig.primaryColor
|
||||
: null,
|
||||
),
|
||||
child: IconButton(
|
||||
tooltip: L10n.of(context)!.toggleLanguages,
|
||||
onPressed: onPressed,
|
||||
icon: const Icon(Icons.translate_outlined),
|
||||
selectedIcon: const Icon(Icons.translate),
|
||||
isSelected: controller.choreographer.messageOptions.isTranslationOn,
|
||||
),
|
||||
);
|
||||
// return Tooltip(
|
||||
// message: L10n.of(context)!.toggleLanguages,
|
||||
// waitDuration: const Duration(milliseconds: 1000),
|
||||
// child: FloatingActionButton(
|
||||
// onPressed: onPressed,
|
||||
// backgroundColor: Colors.white,
|
||||
// mini: false,
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(200), // <-- Radius
|
||||
// ),
|
||||
// child: LanguageFlag(
|
||||
// flagUrl: controller
|
||||
// .choreographer.messageOptions.displayLang?.languageFlag,
|
||||
// size: 50,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,47 @@
|
|||
import 'package:fluffychat/pangea/constants/colors.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fluffychat/pangea/enum/assistance_state_enum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import '../../../pages/chat/chat.dart';
|
||||
|
||||
class ChoreographerSendButton extends StatelessWidget {
|
||||
class ChoreographerSendButton extends StatefulWidget {
|
||||
const ChoreographerSendButton({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
final ChatController controller;
|
||||
|
||||
@override
|
||||
State<ChoreographerSendButton> createState() =>
|
||||
ChoreographerSendButtonState();
|
||||
}
|
||||
|
||||
class ChoreographerSendButtonState extends State<ChoreographerSendButton> {
|
||||
StreamSubscription? _choreoSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
// Rebuild the widget each time there's an update from
|
||||
// choreo. This keeps the spin up-to-date.
|
||||
_choreoSub =
|
||||
widget.controller.choreographer.stateListener.stream.listen((_) {
|
||||
setState(() {});
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_choreoSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// commit for cicd
|
||||
return controller.choreographer.isFetching &&
|
||||
controller.choreographer.isAutoIGCEnabled
|
||||
return widget.controller.choreographer.isFetching &&
|
||||
widget.controller.choreographer.isAutoIGCEnabled
|
||||
? Container(
|
||||
height: 56,
|
||||
width: 56,
|
||||
|
|
@ -28,12 +53,10 @@ class ChoreographerSendButton extends StatelessWidget {
|
|||
alignment: Alignment.center,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.send_outlined),
|
||||
color: controller.choreographer.igc.canSendMessage ||
|
||||
!controller.choreographer.isAutoIGCEnabled
|
||||
? null
|
||||
: PangeaColors.igcError,
|
||||
color: widget.controller.choreographer.assistanceState
|
||||
.stateColor(context),
|
||||
onPressed: () {
|
||||
controller.choreographer.send(context);
|
||||
widget.controller.choreographer.send(context);
|
||||
},
|
||||
tooltip: L10n.of(context)!.send,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,16 +1,8 @@
|
|||
class PLocalKey {
|
||||
static const String user = 'user';
|
||||
|
||||
static const String classes = 'classes';
|
||||
|
||||
static const String access = "access";
|
||||
static const String cachedClassCodeToJoin = "cachedclasscodetojoin";
|
||||
static const String beganWebPayment = "beganWebPayment";
|
||||
|
||||
// making this a random string so that it's harder to guess
|
||||
static const String activatedTrialKey = '7C4EuKIsph';
|
||||
static const String dismissedPaywall = 'dismissedPaywall';
|
||||
static const String paywallBackoff = 'paywallBackoff';
|
||||
static const String autoPlayMessages = 'autoPlayMessages';
|
||||
static const String itAutoPlay = 'itAutoPlay';
|
||||
static const String messagesSinceUpdate = 'messagesSinceLastUpdate';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,15 @@ class ModelKey {
|
|||
static const String l1LanguageKey = 'source_language';
|
||||
static const String publicProfile = 'public';
|
||||
static const String userId = 'user_id';
|
||||
static const String toolSettings = 'tool_settings';
|
||||
static const String userSettings = 'user_settings';
|
||||
static const String instructionsSettings = 'instructions_settings';
|
||||
|
||||
// matrix profile keys
|
||||
// making this a random string so that it's harder to guess
|
||||
static const String activatedTrialKey = '7C4EuKIsph';
|
||||
static const String autoPlayMessages = 'autoPlayMessages';
|
||||
static const String itAutoPlay = 'itAutoPlay';
|
||||
|
||||
static const String clientClassCity = "city";
|
||||
static const String clientClassCountry = "country";
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import 'package:future_loading_dialog/future_loading_dialog.dart';
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import '../../widgets/matrix.dart';
|
||||
import '../utils/bot_name.dart';
|
||||
import '../utils/firebase_analytics.dart';
|
||||
import 'base_controller.dart';
|
||||
|
||||
|
|
@ -49,15 +48,13 @@ class ClassController extends BaseController {
|
|||
Future<void> checkForClassCodeAndSubscription(BuildContext context) async {
|
||||
final String? classCode = _pangeaController.pStoreService.read(
|
||||
PLocalKey.cachedClassCodeToJoin,
|
||||
addClientIdToKey: false,
|
||||
local: true,
|
||||
isAccountData: false,
|
||||
);
|
||||
|
||||
if (classCode != null) {
|
||||
await _pangeaController.pStoreService.delete(
|
||||
PLocalKey.cachedClassCodeToJoin,
|
||||
addClientIdToKey: false,
|
||||
local: true,
|
||||
isAccountData: false,
|
||||
);
|
||||
await joinClasswithCode(
|
||||
context,
|
||||
|
|
@ -69,41 +66,6 @@ class ClassController extends BaseController {
|
|||
}
|
||||
}
|
||||
|
||||
/// if not bot chat return
|
||||
/// if bot chat, get pangeaClassContext
|
||||
/// for all classes not in pangeaClassContext, add bot chat to that class
|
||||
/// PTODO - add analytics bot to all chats and have that do this work
|
||||
Future<List<Room>> addDirectChatsToClasses(Room room) async {
|
||||
if (!room.isDirectChat) return [];
|
||||
final List<String> existingParentsIds =
|
||||
room.pangeaSpaceParents.map((e) => e.id).toList();
|
||||
final List<Room> spaces = _pangeaController.matrixState.client.spacesImIn;
|
||||
|
||||
//make sure we have the latest participants
|
||||
await Future.wait(spaces.map((e) => e.requestParticipants()));
|
||||
|
||||
//get spaces where,
|
||||
//other chat participant is the bot OR is in the space AND the chat is not
|
||||
final List<Room> spacesToAdd = spaces
|
||||
.where(
|
||||
(s) =>
|
||||
(room.directChatMatrixID == BotName.byEnvironment ||
|
||||
s
|
||||
.getParticipants()
|
||||
.map(
|
||||
(u) => u.id,
|
||||
)
|
||||
.contains(room.directChatMatrixID)) &&
|
||||
!existingParentsIds.contains(s.id),
|
||||
)
|
||||
.toList();
|
||||
|
||||
//set the space child for each space
|
||||
return Future.wait(
|
||||
spacesToAdd.map((s) => s.setSpaceChild(room.id, suggested: true)),
|
||||
).then((value) => spaces);
|
||||
}
|
||||
|
||||
Future<void> joinClasswithCode(BuildContext context, String classCode) async {
|
||||
try {
|
||||
final QueryPublicRoomsResponse queryPublicRoomsResponse =
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import '../constants/model_keys.dart';
|
||||
import '../network/requests.dart';
|
||||
import '../network/urls.dart';
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/controllers/language_list_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/models/language_model.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import '../widgets/user_settings/p_language_dialog.dart';
|
||||
|
||||
|
|
@ -18,15 +14,6 @@ class LanguageController {
|
|||
}
|
||||
//show diloag when user does not have languages selected
|
||||
showDialogOnEmptyLanguage(BuildContext dialogContext, Function callback) {
|
||||
if (_pangeaController.userController.userModel?.profile == null) {
|
||||
debugger(when: kDebugMode);
|
||||
Sentry.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message: 'calling showDialogOnEmptyLanguagae with empty user',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!languagesSet) {
|
||||
pLanguageDialog(dialogContext, callback);
|
||||
}
|
||||
|
|
@ -42,13 +29,13 @@ class LanguageController {
|
|||
|
||||
String? get _userL1Code {
|
||||
final source =
|
||||
_pangeaController.userController.userModel?.profile?.sourceLanguage;
|
||||
_pangeaController.userController.profile.userSettings.sourceLanguage;
|
||||
return source == null || source.isEmpty ? null : source;
|
||||
}
|
||||
|
||||
String? get _userL2Code {
|
||||
final target =
|
||||
_pangeaController.userController.userModel?.profile?.targetLanguage;
|
||||
_pangeaController.userController.profile.userSettings.targetLanguage;
|
||||
return target == null || target.isEmpty ? null : target;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@ class PangeaLanguage {
|
|||
}
|
||||
|
||||
static LanguageModel byLangCode(String langCode) {
|
||||
final list = _langList;
|
||||
for (final element in _langList) {
|
||||
if (element.langCode == langCode) return element;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/models/space_model.dart';
|
||||
|
||||
class LocalSettings {
|
||||
late PangeaController _pangeaController;
|
||||
|
||||
LocalSettings(PangeaController pangeaController) : super() {
|
||||
_pangeaController = pangeaController;
|
||||
}
|
||||
|
||||
bool userLanguageToolSetting(ToolSetting setting) {
|
||||
final profileSetting =
|
||||
_pangeaController.pStoreService.read(setting.toString());
|
||||
if (profileSetting != null) {
|
||||
return profileSetting;
|
||||
}
|
||||
return setting == ToolSetting.immersionMode ? false : true;
|
||||
}
|
||||
|
||||
// bool get userEnableIT =>
|
||||
// _pangeaController.pStoreService.read(ToolSetting.interactiveTranslator.toString()) ?? true;
|
||||
|
||||
// bool get userEnableIGC =>
|
||||
// _pangeaController.pStoreService.read(ToolSetting.interactiveGrammar.toString()) ?? true;
|
||||
|
||||
// bool get userImmersionMode =>
|
||||
// _pangeaController.pStoreService.read(ToolSetting.immersionMode.toString()) ?? true;
|
||||
|
||||
// bool get userTranslationsTool =>
|
||||
// _pangeaController.pStoreService.read(ToolSetting.translations.toString()) ?? true;
|
||||
|
||||
// bool get userDefinitionsTool =>
|
||||
// _pangeaController.pStoreService.read(ToolSetting.definitions.toString()) ?? true;
|
||||
}
|
||||
|
|
@ -42,7 +42,6 @@ class AnalyticsController extends BaseController {
|
|||
try {
|
||||
final String? str = _pangeaController.pStoreService.read(
|
||||
_analyticsTimeSpanKey,
|
||||
local: true,
|
||||
);
|
||||
return str != null
|
||||
? TimeSpan.values.firstWhere((e) {
|
||||
|
|
@ -60,7 +59,6 @@ class AnalyticsController extends BaseController {
|
|||
await _pangeaController.pStoreService.save(
|
||||
_analyticsTimeSpanKey,
|
||||
timeSpan.toString(),
|
||||
local: true,
|
||||
);
|
||||
setState();
|
||||
}
|
||||
|
|
@ -72,7 +70,6 @@ class AnalyticsController extends BaseController {
|
|||
try {
|
||||
final String? str = _pangeaController.pStoreService.read(
|
||||
_analyticsSpaceLangKey,
|
||||
local: true,
|
||||
);
|
||||
return str != null
|
||||
? PangeaLanguage.byLangCode(str)
|
||||
|
|
@ -88,7 +85,6 @@ class AnalyticsController extends BaseController {
|
|||
await _pangeaController.pStoreService.save(
|
||||
_analyticsSpaceLangKey,
|
||||
lang.langCode,
|
||||
local: true,
|
||||
);
|
||||
setState();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'
|
|||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import '../extensions/client_extension/client_extension.dart';
|
||||
import '../extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||
|
|
@ -113,20 +114,38 @@ class MyAnalyticsController {
|
|||
// adds an event ID to the cache of un-added event IDs
|
||||
// if the event IDs isn't already added
|
||||
void addMessageSinceUpdate(String eventId) {
|
||||
final List<String> currentCache = messagesSinceUpdate;
|
||||
if (!currentCache.contains(eventId)) {
|
||||
currentCache.add(eventId);
|
||||
_pangeaController.pStoreService.save(
|
||||
PLocalKey.messagesSinceUpdate,
|
||||
currentCache,
|
||||
local: true,
|
||||
);
|
||||
}
|
||||
try {
|
||||
final List<String> currentCache = messagesSinceUpdate;
|
||||
if (!currentCache.contains(eventId)) {
|
||||
currentCache.add(eventId);
|
||||
_pangeaController.pStoreService.save(
|
||||
PLocalKey.messagesSinceUpdate,
|
||||
currentCache,
|
||||
);
|
||||
}
|
||||
|
||||
// if the cached has reached if max-length, update analytics
|
||||
if (messagesSinceUpdate.length > _maxMessagesCached) {
|
||||
debugPrint("reached max messages, updating");
|
||||
updateAnalytics();
|
||||
// if the cached has reached if max-length, update analytics
|
||||
if (messagesSinceUpdate.length > _maxMessagesCached) {
|
||||
debugPrint("reached max messages, updating");
|
||||
updateAnalytics();
|
||||
}
|
||||
} catch (exception, stackTrace) {
|
||||
ErrorHandler.logError(
|
||||
e: PangeaWarningError("Failed to add message since update: $exception"),
|
||||
s: stackTrace,
|
||||
m: 'Failed to add message since update for eventId: $eventId',
|
||||
);
|
||||
Sentry.captureException(
|
||||
exception,
|
||||
stackTrace: stackTrace,
|
||||
withScope: (scope) {
|
||||
scope.setExtra(
|
||||
'extra_info',
|
||||
'Failed during addMessageSinceUpdate with eventId: $eventId',
|
||||
);
|
||||
scope.setTag('where', 'addMessageSinceUpdate');
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +154,6 @@ class MyAnalyticsController {
|
|||
_pangeaController.pStoreService.save(
|
||||
PLocalKey.messagesSinceUpdate,
|
||||
[],
|
||||
local: true,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -143,26 +161,42 @@ class MyAnalyticsController {
|
|||
// it's possible for this cache to be invalid or deleted
|
||||
// It's a proxy measure for messages sent since last update
|
||||
List<String> get messagesSinceUpdate {
|
||||
final dynamic locallySaved = _pangeaController.pStoreService.read(
|
||||
PLocalKey.messagesSinceUpdate,
|
||||
local: true,
|
||||
);
|
||||
if (locallySaved == null) {
|
||||
_pangeaController.pStoreService.save(
|
||||
PLocalKey.messagesSinceUpdate,
|
||||
[],
|
||||
local: true,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
return locallySaved as List<String>;
|
||||
} catch (err) {
|
||||
Logs().d('Reading messages since update from local storage');
|
||||
final dynamic locallySaved = _pangeaController.pStoreService.read(
|
||||
PLocalKey.messagesSinceUpdate,
|
||||
);
|
||||
if (locallySaved == null) {
|
||||
Logs().d('No locally saved messages found, initializing empty list.');
|
||||
_pangeaController.pStoreService.save(
|
||||
PLocalKey.messagesSinceUpdate,
|
||||
[],
|
||||
);
|
||||
return [];
|
||||
}
|
||||
return locallySaved.cast<String>();
|
||||
} catch (exception, stackTrace) {
|
||||
ErrorHandler.logError(
|
||||
e: PangeaWarningError(
|
||||
"Failed to get messages since update: $exception",
|
||||
),
|
||||
s: stackTrace,
|
||||
m: 'Failed to retrieve messages since update',
|
||||
);
|
||||
Sentry.captureException(
|
||||
exception,
|
||||
stackTrace: stackTrace,
|
||||
withScope: (scope) {
|
||||
scope.setExtra(
|
||||
'extra_info',
|
||||
'Error during messagesSinceUpdate getter',
|
||||
);
|
||||
scope.setTag('where', 'messagesSinceUpdate');
|
||||
},
|
||||
);
|
||||
_pangeaController.pStoreService.save(
|
||||
PLocalKey.messagesSinceUpdate,
|
||||
[],
|
||||
local: true,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
|
@ -195,11 +229,8 @@ class MyAnalyticsController {
|
|||
/// top level analytics sending function. Gather recent messages and activity records,
|
||||
/// convert them into the correct formats, and send them to the analytics room
|
||||
Future<void> _updateAnalytics() async {
|
||||
// if missing important info, don't send analytics
|
||||
if (userL2 == null || _client.userID == null) {
|
||||
debugger(when: kDebugMode);
|
||||
return;
|
||||
}
|
||||
// if missing important info, don't send analytics. Could happen if user just signed up.
|
||||
if (userL2 == null || _client.userID == null) return;
|
||||
|
||||
// analytics room for the user and current target language
|
||||
final Room analyticsRoom = await _client.getMyAnalyticsRoom(userL2!);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import 'package:fluffychat/pangea/controllers/contextual_definition_controller.d
|
|||
import 'package:fluffychat/pangea/controllers/language_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/language_detection_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/language_list_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/local_settings.dart';
|
||||
import 'package:fluffychat/pangea/controllers/message_data_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/permissions_controller.dart';
|
||||
|
|
@ -47,7 +46,6 @@ class PangeaController {
|
|||
late AnalyticsController analytics;
|
||||
late MyAnalyticsController myAnalytics;
|
||||
late WordController wordNet;
|
||||
late LocalSettings localSettings;
|
||||
late MessageDataController messageData;
|
||||
late ContextualDefinitionController definitions;
|
||||
late ITFeedbackController itFeedback;
|
||||
|
|
@ -60,7 +58,7 @@ class PangeaController {
|
|||
late PracticeGenerationController practiceGenerationController;
|
||||
|
||||
///store Services
|
||||
late PLocalStore pStoreService;
|
||||
late PStore pStoreService;
|
||||
final pLanguageStore = PangeaLanguage();
|
||||
|
||||
///Matrix Variables
|
||||
|
|
@ -89,10 +87,9 @@ class PangeaController {
|
|||
|
||||
/// Initialize controllers
|
||||
_addRefInObjects() {
|
||||
pStoreService = PLocalStore(pangeaController: this);
|
||||
pStoreService = PStore(pangeaController: this);
|
||||
userController = UserController(this);
|
||||
languageController = LanguageController(this);
|
||||
localSettings = LocalSettings(this);
|
||||
classController = ClassController(this);
|
||||
permissionsController = PermissionsController(this);
|
||||
analytics = AnalyticsController(this);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ 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';
|
||||
import 'package:fluffychat/pangea/models/space_model.dart';
|
||||
import 'package:fluffychat/pangea/models/user_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/p_extension.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
|
|
@ -32,12 +31,9 @@ class PermissionsController extends BaseController {
|
|||
|
||||
/// Returns false if user is null
|
||||
bool isUser18() {
|
||||
final dob = _pangeaController.pStoreService.read(
|
||||
MatrixProfile.dateOfBirth.title,
|
||||
);
|
||||
return dob != null
|
||||
? DateTime.parse(dob).isAtLeastYearsOld(AgeLimits.toAccessFeatures)
|
||||
: false;
|
||||
final DateTime? dob =
|
||||
_pangeaController.userController.profile.userSettings.dateOfBirth;
|
||||
return dob?.isAtLeastYearsOld(AgeLimits.toAccessFeatures) ?? false;
|
||||
}
|
||||
|
||||
/// A user can private chat if
|
||||
|
|
@ -99,8 +95,26 @@ class PermissionsController extends BaseController {
|
|||
return classPermission == 0;
|
||||
}
|
||||
|
||||
bool userToolSetting(ToolSetting setting) =>
|
||||
_pangeaController.localSettings.userLanguageToolSetting(setting);
|
||||
bool userToolSetting(ToolSetting setting) {
|
||||
switch (setting) {
|
||||
case ToolSetting.interactiveTranslator:
|
||||
return _pangeaController
|
||||
.userController.profile.toolSettings.interactiveTranslator;
|
||||
case ToolSetting.interactiveGrammar:
|
||||
return _pangeaController
|
||||
.userController.profile.toolSettings.interactiveGrammar;
|
||||
case ToolSetting.immersionMode:
|
||||
return _pangeaController
|
||||
.userController.profile.toolSettings.immersionMode;
|
||||
case ToolSetting.definitions:
|
||||
return _pangeaController
|
||||
.userController.profile.toolSettings.definitions;
|
||||
case ToolSetting.autoIGC:
|
||||
return _pangeaController.userController.profile.toolSettings.autoIGC;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool isToolEnabled(ToolSetting setting, Room? room) {
|
||||
if (room?.isSpaceAdmin ?? false) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import 'package:fluffychat/pangea/controllers/base_controller.dart';
|
|||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/models/base_subscription_info.dart';
|
||||
import 'package:fluffychat/pangea/models/mobile_subscriptions.dart';
|
||||
import 'package:fluffychat/pangea/models/user_model.dart';
|
||||
import 'package:fluffychat/pangea/models/web_subscriptions.dart';
|
||||
import 'package:fluffychat/pangea/network/requests.dart';
|
||||
import 'package:fluffychat/pangea/network/urls.dart';
|
||||
|
|
@ -97,12 +96,10 @@ class SubscriptionController extends BaseController {
|
|||
} else {
|
||||
final bool? beganWebPayment = _pangeaController.pStoreService.read(
|
||||
PLocalKey.beganWebPayment,
|
||||
local: true,
|
||||
);
|
||||
if (beganWebPayment ?? false) {
|
||||
await _pangeaController.pStoreService.delete(
|
||||
PLocalKey.beganWebPayment,
|
||||
local: true,
|
||||
);
|
||||
if (_pangeaController.subscriptionController.isSubscribed) {
|
||||
subscriptionStream.add(true);
|
||||
|
|
@ -142,7 +139,6 @@ class SubscriptionController extends BaseController {
|
|||
await _pangeaController.pStoreService.save(
|
||||
PLocalKey.beganWebPayment,
|
||||
true,
|
||||
local: true,
|
||||
);
|
||||
setState();
|
||||
launchUrlString(
|
||||
|
|
@ -182,37 +178,35 @@ class SubscriptionController extends BaseController {
|
|||
}
|
||||
}
|
||||
|
||||
bool get _activatedNewUserTrial =>
|
||||
_pangeaController.userController.inTrialWindow &&
|
||||
(_pangeaController.pStoreService.read(
|
||||
MatrixProfile.activatedFreeTrial.title,
|
||||
) ??
|
||||
false);
|
||||
bool get _activatedNewUserTrial {
|
||||
final bool activated = _pangeaController
|
||||
.userController.profile.userSettings.activatedFreeTrial;
|
||||
return _pangeaController.userController.inTrialWindow && activated;
|
||||
}
|
||||
|
||||
void activateNewUserTrial() {
|
||||
_pangeaController.pStoreService
|
||||
.save(
|
||||
MatrixProfile.activatedFreeTrial.title,
|
||||
true,
|
||||
)
|
||||
.then((_) {
|
||||
setNewUserTrial();
|
||||
trialActivationStream.add(true);
|
||||
});
|
||||
_pangeaController.userController.updateProfile(
|
||||
(profile) {
|
||||
profile.userSettings.activatedFreeTrial = true;
|
||||
return profile;
|
||||
},
|
||||
);
|
||||
setNewUserTrial();
|
||||
trialActivationStream.add(true);
|
||||
}
|
||||
|
||||
void setNewUserTrial() {
|
||||
if (_pangeaController.userController.userModel?.profile == null) {
|
||||
final DateTime? createdAt =
|
||||
_pangeaController.userController.profile.userSettings.createdAt;
|
||||
if (createdAt == null) {
|
||||
ErrorHandler.logError(
|
||||
m: "Null user profile in subscription settings",
|
||||
m: "Null user profile createAt in subscription settings",
|
||||
s: StackTrace.current,
|
||||
);
|
||||
return;
|
||||
}
|
||||
final String profileCreatedAt =
|
||||
_pangeaController.userController.userModel!.profile!.createdAt;
|
||||
final DateTime creationTimestamp = DateTime.parse(profileCreatedAt);
|
||||
final DateTime expirationDate = creationTimestamp.add(
|
||||
|
||||
final DateTime expirationDate = createdAt.add(
|
||||
const Duration(days: 7),
|
||||
);
|
||||
subscription?.setTrial(expirationDate);
|
||||
|
|
@ -242,7 +236,6 @@ class SubscriptionController extends BaseController {
|
|||
DateTime? get _lastDismissedPaywall {
|
||||
final lastDismissed = _pangeaController.pStoreService.read(
|
||||
PLocalKey.dismissedPaywall,
|
||||
local: true,
|
||||
);
|
||||
if (lastDismissed == null) return null;
|
||||
return DateTime.tryParse(lastDismissed);
|
||||
|
|
@ -251,7 +244,6 @@ class SubscriptionController extends BaseController {
|
|||
int? get _paywallBackoff {
|
||||
final backoff = _pangeaController.pStoreService.read(
|
||||
PLocalKey.paywallBackoff,
|
||||
local: true,
|
||||
);
|
||||
if (backoff == null) return null;
|
||||
return backoff;
|
||||
|
|
@ -269,20 +261,17 @@ class SubscriptionController extends BaseController {
|
|||
await _pangeaController.pStoreService.save(
|
||||
PLocalKey.dismissedPaywall,
|
||||
DateTime.now().toString(),
|
||||
local: true,
|
||||
);
|
||||
|
||||
if (_paywallBackoff == null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
PLocalKey.paywallBackoff,
|
||||
1,
|
||||
local: true,
|
||||
);
|
||||
} else {
|
||||
await _pangeaController.pStoreService.save(
|
||||
PLocalKey.paywallBackoff,
|
||||
_paywallBackoff! + 1,
|
||||
local: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ import 'dart:async';
|
|||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fluffychat/pangea/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/controllers/base_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:jwt_decode/jwt_decode.dart';
|
||||
import 'package:matrix/matrix.dart' as matrix;
|
||||
|
||||
|
|
@ -13,447 +13,247 @@ import '../constants/local.key.dart';
|
|||
import '../models/user_model.dart';
|
||||
import '../repo/user_repo.dart';
|
||||
|
||||
/// Controller that manages saving and reading of user/profile information
|
||||
class UserController extends BaseController {
|
||||
late PangeaController _pangeaController;
|
||||
final Completer _completer = Completer();
|
||||
UserController(PangeaController pangeaController) : super() {
|
||||
_pangeaController = pangeaController;
|
||||
}
|
||||
|
||||
Future<void> createPangeaUser({required String dob}) async {
|
||||
final PUserModel newUserModel = await PUserRepo.repoCreatePangeaUser(
|
||||
userID: userId!,
|
||||
fullName: fullname,
|
||||
dob: dob,
|
||||
matrixAccessToken: _matrixAccessToken!,
|
||||
);
|
||||
newUserModel.save(_pangeaController);
|
||||
await updateMatrixProfile(dateOfBirth: dob);
|
||||
/// Convenience function that returns the user ID currently stored in the client.
|
||||
String? get userId => _pangeaController.matrixState.client.userID;
|
||||
|
||||
/// Convenience function that returns the accessToken currently stored in the client.
|
||||
String? get _matrixAccessToken =>
|
||||
_pangeaController.matrixState.client.accessToken;
|
||||
|
||||
/// Cached version of the user profile, so it doesn't have
|
||||
/// to be read in from client's account data each time it is accessed.
|
||||
Profile? _cachedProfile;
|
||||
|
||||
/// Listens for account updates and updates the cached profile
|
||||
StreamSubscription? _profileListener;
|
||||
|
||||
/// Listen for updates to account data in syncs and update the cached profile
|
||||
void addProfileListener() {
|
||||
_profileListener ??= _pangeaController.matrixState.client.onSync.stream
|
||||
.where((sync) => sync.accountData != null)
|
||||
.listen((sync) {
|
||||
final Profile? fromAccountData = Profile.fromAccountData();
|
||||
if (fromAccountData != null) {
|
||||
_cachedProfile = fromAccountData;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<PUserModel?> fetchUserModel() async {
|
||||
try {
|
||||
if (_matrixAccessToken == null) {
|
||||
throw Exception(
|
||||
"calling fetchUserModel with matrixAccesstoken == null",
|
||||
);
|
||||
}
|
||||
/// The user's profile. Will be empty if the client's accountData hasn't
|
||||
/// been loaded yet (if the first sync hasn't gone through yet)
|
||||
/// or if the user hasn't yer set their date of birth.
|
||||
Profile get profile {
|
||||
/// if the profile is cached, return it
|
||||
if (_cachedProfile != null) return _cachedProfile!;
|
||||
|
||||
final PUserModel? newUserModel = await PUserRepo.fetchPangeaUserInfo(
|
||||
/// if account data is empty, return an empty profile
|
||||
if (_pangeaController.matrixState.client.accountData.isEmpty) {
|
||||
return Profile.emptyProfile;
|
||||
}
|
||||
|
||||
/// try to get the account data in the up-to-date format
|
||||
final Profile? fromAccountData = Profile.fromAccountData();
|
||||
if (fromAccountData != null) {
|
||||
_cachedProfile = fromAccountData;
|
||||
return fromAccountData;
|
||||
}
|
||||
|
||||
_cachedProfile = Profile.migrateFromAccountData();
|
||||
_cachedProfile?.saveProfileData();
|
||||
return _cachedProfile ?? Profile.emptyProfile;
|
||||
}
|
||||
|
||||
/// Updates the user's profile with the given [update] function and saves it.
|
||||
void updateProfile(Profile Function(Profile) update) {
|
||||
final Profile updatedProfile = update(profile);
|
||||
updatedProfile.saveProfileData();
|
||||
}
|
||||
|
||||
/// Creates a new profile for the user with the given date of birth.
|
||||
Future<void> createProfile({required DateTime dob}) async {
|
||||
final userSettings = UserSettings(
|
||||
dateOfBirth: dob,
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
final newProfile = Profile(userSettings: userSettings);
|
||||
await newProfile.saveProfileData(waitForDataInSync: true);
|
||||
}
|
||||
|
||||
/// A completer for the profile model of a user.
|
||||
Completer<void>? _profileCompleter;
|
||||
|
||||
/// Initializes the user's profile. Runs a function to wait for account data to load,
|
||||
/// read account data into profile, and migrate any missing info from the pangea profile.
|
||||
/// Finally, it adds a listen to update the profile data when new account data comes in.
|
||||
Future<void> initialize() async {
|
||||
if (_profileCompleter?.isCompleted ?? false) {
|
||||
return _profileCompleter!.future;
|
||||
}
|
||||
|
||||
if (_profileCompleter != null) {
|
||||
await _profileCompleter!.future;
|
||||
return _profileCompleter!.future;
|
||||
}
|
||||
|
||||
_profileCompleter = Completer<void>();
|
||||
|
||||
try {
|
||||
await _initialize();
|
||||
addProfileListener();
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(e: err, s: s);
|
||||
} finally {
|
||||
_profileCompleter!.complete();
|
||||
}
|
||||
|
||||
return _profileCompleter!.future;
|
||||
}
|
||||
|
||||
/// Initializes the user's profile by waiting for account data to load, reading in account
|
||||
/// data to profile, and migrating from the pangea profile if the account data is not present.
|
||||
Future<void> _initialize() async {
|
||||
await _pangeaController.matrixState.client.waitForAccountData();
|
||||
if (profile.userSettings.dateOfBirth != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final PangeaProfileResponse? resp = await PUserRepo.fetchPangeaUserInfo(
|
||||
userID: userId!,
|
||||
matrixAccessToken: _matrixAccessToken!,
|
||||
);
|
||||
if (resp?.profile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final userSetting = UserSettings.fromJson(resp!.profile.toJson());
|
||||
final newProfile = Profile(userSettings: userSetting);
|
||||
await newProfile.saveProfileData(waitForDataInSync: true);
|
||||
}
|
||||
|
||||
/// Reinitializes the user's profile
|
||||
/// This method should be called whenever the user's login status changes
|
||||
Future<void> reinitialize() async {
|
||||
_profileCompleter = null;
|
||||
_cachedProfile = null;
|
||||
await initialize();
|
||||
}
|
||||
|
||||
/// Returns a boolean value indicating whether a new JWT (JSON Web Token) is needed.
|
||||
bool needNewJWT(String token) => Jwt.isExpired(token);
|
||||
|
||||
/// Retrieves the access token for the user. Looks for it locally,
|
||||
/// and if it's not found or expired, fetches it from the server.
|
||||
Future<String> get accessToken async {
|
||||
final localAccessToken =
|
||||
_pangeaController.pStoreService.read(PLocalKey.access);
|
||||
|
||||
if (localAccessToken == null || needNewJWT(localAccessToken)) {
|
||||
final PangeaProfileResponse? userModel =
|
||||
await PUserRepo.fetchPangeaUserInfo(
|
||||
userID: userId!,
|
||||
matrixAccessToken: _matrixAccessToken!,
|
||||
);
|
||||
newUserModel?.save(_pangeaController);
|
||||
await migrateMatrixProfile();
|
||||
_completeCompleter();
|
||||
|
||||
return newUserModel;
|
||||
} catch (err) {
|
||||
debugPrint(
|
||||
"User model not found. Probably first signup and needs Pangea account",
|
||||
if (userModel?.access == null) {
|
||||
throw ("Trying to get accessToken with null userModel");
|
||||
}
|
||||
_pangeaController.pStoreService.save(
|
||||
PLocalKey.access,
|
||||
userModel!.access,
|
||||
);
|
||||
rethrow;
|
||||
return userModel.access;
|
||||
}
|
||||
|
||||
return localAccessToken;
|
||||
}
|
||||
|
||||
dynamic migratedProfileInfo(MatrixProfile key) {
|
||||
final dynamic localValue = _pangeaController.pStoreService.read(
|
||||
key.title,
|
||||
local: true,
|
||||
);
|
||||
final dynamic matrixValue = _pangeaController.pStoreService.read(
|
||||
key.title,
|
||||
);
|
||||
return localValue != null && matrixValue != localValue ? localValue : null;
|
||||
}
|
||||
|
||||
Future<void> migrateMatrixProfile() async {
|
||||
final Profile? pangeaProfile = userModel?.profile;
|
||||
|
||||
final String? pangeaDob = pangeaProfile?.dateOfBirth;
|
||||
final String? matrixDob = _pangeaController.pStoreService.read(
|
||||
ModelKey.userDateOfBirth,
|
||||
);
|
||||
final String? dob =
|
||||
pangeaDob != null && matrixDob != pangeaDob ? pangeaDob : null;
|
||||
|
||||
final pangeaCreatedAt = pangeaProfile?.createdAt;
|
||||
final matrixCreatedAt = _pangeaController.pStoreService.read(
|
||||
MatrixProfile.createdAt.title,
|
||||
);
|
||||
final String? createdAt =
|
||||
pangeaCreatedAt != null && matrixCreatedAt != pangeaCreatedAt
|
||||
? pangeaCreatedAt
|
||||
: null;
|
||||
|
||||
final String? pangeaTargetLanguage = pangeaProfile?.targetLanguage;
|
||||
final String? matrixTargetLanguage = _pangeaController.pStoreService.read(
|
||||
MatrixProfile.targetLanguage.title,
|
||||
);
|
||||
final String? targetLanguage = pangeaTargetLanguage != null &&
|
||||
matrixTargetLanguage != pangeaTargetLanguage
|
||||
? pangeaTargetLanguage
|
||||
: null;
|
||||
|
||||
final String? pangeaSourceLanguage = pangeaProfile?.sourceLanguage;
|
||||
final String? matrixSourceLanguage = _pangeaController.pStoreService.read(
|
||||
MatrixProfile.sourceLanguage.title,
|
||||
);
|
||||
final String? sourceLanguage = pangeaSourceLanguage != null &&
|
||||
matrixSourceLanguage != pangeaSourceLanguage
|
||||
? pangeaSourceLanguage
|
||||
: null;
|
||||
|
||||
final String? pangeaCountry = pangeaProfile?.country;
|
||||
final String? matrixCountry = _pangeaController.pStoreService.read(
|
||||
MatrixProfile.country.title,
|
||||
);
|
||||
final String? country =
|
||||
pangeaCountry != null && matrixCountry != pangeaCountry
|
||||
? pangeaCountry
|
||||
: null;
|
||||
|
||||
final bool? pangeaPublicProfile = pangeaProfile?.publicProfile;
|
||||
final bool? matrixPublicProfile = _pangeaController.pStoreService.read(
|
||||
MatrixProfile.publicProfile.title,
|
||||
);
|
||||
final bool? publicProfile = pangeaPublicProfile != null &&
|
||||
matrixPublicProfile != pangeaPublicProfile
|
||||
? pangeaPublicProfile
|
||||
: null;
|
||||
|
||||
final bool? autoPlay = migratedProfileInfo(MatrixProfile.autoPlayMessages);
|
||||
final bool? itAutoPlay = migratedProfileInfo(MatrixProfile.itAutoPlay);
|
||||
final bool? trial = migratedProfileInfo(MatrixProfile.activatedFreeTrial);
|
||||
final bool? interactiveTranslator =
|
||||
migratedProfileInfo(MatrixProfile.interactiveTranslator);
|
||||
final bool? interactiveGrammar =
|
||||
migratedProfileInfo(MatrixProfile.interactiveGrammar);
|
||||
final bool? immersionMode =
|
||||
migratedProfileInfo(MatrixProfile.immersionMode);
|
||||
final bool? definitions = migratedProfileInfo(MatrixProfile.definitions);
|
||||
// final bool? translations = migratedProfileInfo(MatrixProfile.translations);
|
||||
final bool? showItInstructions =
|
||||
migratedProfileInfo(MatrixProfile.showedItInstructions);
|
||||
final bool? showClickMessage =
|
||||
migratedProfileInfo(MatrixProfile.showedClickMessage);
|
||||
final bool? showBlurMeansTranslate =
|
||||
migratedProfileInfo(MatrixProfile.showedBlurMeansTranslate);
|
||||
|
||||
await updateMatrixProfile(
|
||||
dateOfBirth: dob,
|
||||
autoPlayMessages: autoPlay,
|
||||
itAutoPlay: itAutoPlay,
|
||||
activatedFreeTrial: trial,
|
||||
interactiveTranslator: interactiveTranslator,
|
||||
interactiveGrammar: interactiveGrammar,
|
||||
immersionMode: immersionMode,
|
||||
definitions: definitions,
|
||||
// translations: translations,
|
||||
showedItInstructions: showItInstructions,
|
||||
showedClickMessage: showClickMessage,
|
||||
showedBlurMeansTranslate: showBlurMeansTranslate,
|
||||
createdAt: createdAt,
|
||||
targetLanguage: targetLanguage,
|
||||
sourceLanguage: sourceLanguage,
|
||||
country: country,
|
||||
publicProfile: publicProfile,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateUserProfile({
|
||||
String? dateOfBirth,
|
||||
String? targetLanguage,
|
||||
String? sourceLanguage,
|
||||
String? country,
|
||||
List<String>? interests,
|
||||
List<String>? speaks,
|
||||
bool? publicProfile,
|
||||
}) async {
|
||||
if (userModel == null) throw Exception("Local userModel not defined");
|
||||
final profileJson = userModel!.profile!.toJson();
|
||||
|
||||
if (dateOfBirth != null) {
|
||||
profileJson[ModelKey.userDateOfBirth] = dateOfBirth;
|
||||
}
|
||||
if (targetLanguage != null) {
|
||||
profileJson[ModelKey.userTargetLanguage] = targetLanguage;
|
||||
}
|
||||
if (sourceLanguage != null) {
|
||||
profileJson[ModelKey.userSourceLanguage] = sourceLanguage;
|
||||
}
|
||||
if (interests != null) {
|
||||
profileJson[ModelKey.userInterests] = interests.toString();
|
||||
}
|
||||
if (speaks != null) {
|
||||
profileJson[ModelKey.userSpeaks] = speaks.toString();
|
||||
}
|
||||
if (country != null) {
|
||||
profileJson[ModelKey.userCountry] = country;
|
||||
}
|
||||
if (publicProfile != null) {
|
||||
profileJson[ModelKey.publicProfile] = publicProfile;
|
||||
}
|
||||
final Profile updatedUserProfile = await PUserRepo.updateUserProfile(
|
||||
Profile.fromJson(profileJson),
|
||||
await accessToken,
|
||||
);
|
||||
|
||||
PUserModel(
|
||||
access: await accessToken,
|
||||
refresh: userModel!.refresh,
|
||||
profile: updatedUserProfile,
|
||||
).save(_pangeaController);
|
||||
|
||||
await updateMatrixProfile(
|
||||
dateOfBirth: dateOfBirth,
|
||||
targetLanguage: targetLanguage,
|
||||
sourceLanguage: sourceLanguage,
|
||||
country: country,
|
||||
publicProfile: publicProfile,
|
||||
);
|
||||
}
|
||||
|
||||
PUserModel? get userModel {
|
||||
final data = _pangeaController.pStoreService.read(
|
||||
PLocalKey.user,
|
||||
local: true,
|
||||
);
|
||||
return data != null ? PUserModel.fromJson(data) : null;
|
||||
}
|
||||
|
||||
Future<void> updateMatrixProfile({
|
||||
String? dateOfBirth,
|
||||
bool? autoPlayMessages,
|
||||
bool? itAutoPlay,
|
||||
bool? activatedFreeTrial,
|
||||
bool? interactiveTranslator,
|
||||
bool? interactiveGrammar,
|
||||
bool? immersionMode,
|
||||
bool? definitions,
|
||||
// bool? translations,
|
||||
bool? showedItInstructions,
|
||||
bool? showedClickMessage,
|
||||
bool? showedBlurMeansTranslate,
|
||||
bool? showedTooltipInstructions,
|
||||
String? createdAt,
|
||||
String? targetLanguage,
|
||||
String? sourceLanguage,
|
||||
String? country,
|
||||
bool? publicProfile,
|
||||
}) async {
|
||||
if (dateOfBirth != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.dateOfBirth.title,
|
||||
dateOfBirth,
|
||||
);
|
||||
}
|
||||
if (autoPlayMessages != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.autoPlayMessages.title,
|
||||
autoPlayMessages,
|
||||
);
|
||||
}
|
||||
if (itAutoPlay != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.itAutoPlay.title,
|
||||
itAutoPlay,
|
||||
);
|
||||
}
|
||||
if (activatedFreeTrial != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.activatedFreeTrial.title,
|
||||
activatedFreeTrial,
|
||||
);
|
||||
}
|
||||
if (interactiveTranslator != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.interactiveTranslator.title,
|
||||
interactiveTranslator,
|
||||
);
|
||||
}
|
||||
if (interactiveGrammar != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.interactiveGrammar.title,
|
||||
interactiveGrammar,
|
||||
);
|
||||
}
|
||||
if (immersionMode != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.immersionMode.title,
|
||||
immersionMode,
|
||||
);
|
||||
}
|
||||
if (definitions != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.definitions.title,
|
||||
definitions,
|
||||
);
|
||||
}
|
||||
// if (translations != null) {
|
||||
// await _pangeaController.pStoreService.save(
|
||||
// MatrixProfile.translations.title,
|
||||
// translations,
|
||||
// );
|
||||
// }
|
||||
if (showedItInstructions != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.showedItInstructions.title,
|
||||
showedItInstructions,
|
||||
);
|
||||
}
|
||||
if (showedClickMessage != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.showedClickMessage.title,
|
||||
showedClickMessage,
|
||||
);
|
||||
}
|
||||
if (showedBlurMeansTranslate != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.showedBlurMeansTranslate.title,
|
||||
showedBlurMeansTranslate,
|
||||
);
|
||||
}
|
||||
if (showedTooltipInstructions != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.showedTooltipInstructions.title,
|
||||
showedTooltipInstructions,
|
||||
);
|
||||
}
|
||||
if (createdAt != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.createdAt.title,
|
||||
createdAt,
|
||||
);
|
||||
}
|
||||
if (targetLanguage != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.targetLanguage.title,
|
||||
targetLanguage,
|
||||
);
|
||||
}
|
||||
if (sourceLanguage != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.sourceLanguage.title,
|
||||
sourceLanguage,
|
||||
);
|
||||
}
|
||||
if (country != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.country.title,
|
||||
country,
|
||||
);
|
||||
}
|
||||
if (publicProfile != null) {
|
||||
await _pangeaController.pStoreService.save(
|
||||
MatrixProfile.publicProfile.title,
|
||||
publicProfile,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _completeCompleter() {
|
||||
if (!_completer.isCompleted) {
|
||||
_completer.complete(null);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Completer> get completer async {
|
||||
if (await isPUserDataAvailable) {
|
||||
_completeCompleter();
|
||||
}
|
||||
return _completer;
|
||||
}
|
||||
|
||||
bool get needNewJWT =>
|
||||
userModel?.access != null ? Jwt.isExpired(userModel!.access) : true;
|
||||
|
||||
Future<String> get accessToken async {
|
||||
await (await completer).future;
|
||||
// if userModel null or access token expired then fetchUserModel
|
||||
final PUserModel? useThisOne =
|
||||
needNewJWT ? await fetchUserModel() : userModel;
|
||||
|
||||
if (useThisOne == null) {
|
||||
//debugger(when: kDebugMode);
|
||||
throw Exception("trying to get accessToken with userModel = null");
|
||||
}
|
||||
return useThisOne.access;
|
||||
}
|
||||
|
||||
String? get userId {
|
||||
return _pangeaController.matrixState.client.userID;
|
||||
}
|
||||
|
||||
String get fullname {
|
||||
final String? userID = userId;
|
||||
if (userID == null) {
|
||||
throw Exception('User ID not found');
|
||||
}
|
||||
return userID.substring(0, userID.indexOf(":")).replaceAll("@", "");
|
||||
}
|
||||
|
||||
Future<bool> get isPUserDataAvailable async {
|
||||
try {
|
||||
final PUserModel? toCheck = userModel ?? (await fetchUserModel());
|
||||
return toCheck != null ? true : false;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
/// Returns the full name of the user.
|
||||
/// If the [userId] is null, an error will be logged and null will be returned.
|
||||
/// The full name is obtained by extracting the substring before the first occurrence of ":" in the [userId]
|
||||
/// and then replacing all occurrences of "@" with an empty string.
|
||||
String? get fullname {
|
||||
if (userId == null) {
|
||||
ErrorHandler.logError(
|
||||
e: "calling fullname with userId == null",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return userId!.substring(0, userId!.indexOf(":")).replaceAll("@", "");
|
||||
}
|
||||
|
||||
/// Checks if user data is available and the date of birth is set.
|
||||
/// Returns a [Future] that completes with a [bool] value indicating
|
||||
/// whether the user data is available and the date of birth is set.
|
||||
Future<bool> get isUserDataAvailableAndDateOfBirthSet async {
|
||||
try {
|
||||
final client = _pangeaController.matrixState.client;
|
||||
if (client.prevBatch == null) {
|
||||
await client.onSync.stream.first;
|
||||
}
|
||||
await fetchUserModel();
|
||||
final localAccountData = _pangeaController.pStoreService.read(
|
||||
ModelKey.userDateOfBirth,
|
||||
);
|
||||
return localAccountData != null;
|
||||
} catch (err) {
|
||||
// the function fetchUserModel() uses a completer, so it shouldn't
|
||||
// re-call the endpoint if it has already been called
|
||||
await initialize();
|
||||
return profile.userSettings.dateOfBirth != null;
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(e: err, s: s);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a boolean value indicating whether the user is currently in the trial window.
|
||||
bool get inTrialWindow {
|
||||
final String? createdAt = userModel?.profile?.createdAt;
|
||||
final DateTime? createdAt = profile.userSettings.createdAt;
|
||||
if (createdAt == null) {
|
||||
return false;
|
||||
}
|
||||
return DateTime.parse(createdAt).isAfter(
|
||||
return createdAt.isAfter(
|
||||
DateTime.now().subtract(const Duration(days: 7)),
|
||||
);
|
||||
}
|
||||
|
||||
/// Checks if the user's languages are set.
|
||||
/// Returns a [Future] that completes with a [bool] value
|
||||
/// indicating whether the user's languages are set.
|
||||
///
|
||||
/// A user's languages are considered set if the source and target languages
|
||||
/// are not null, not empty, and not equal to the [LanguageKeys.unknownLanguage] constant.
|
||||
///
|
||||
/// If an error occurs during the process, it logs the error and returns `false`.
|
||||
Future<bool> get areUserLanguagesSet async {
|
||||
try {
|
||||
final PUserModel? toCheck = userModel ?? (await fetchUserModel());
|
||||
if (toCheck?.profile == null) {
|
||||
return false;
|
||||
}
|
||||
final String? srcLang = toCheck!.profile!.sourceLanguage;
|
||||
final String? tgtLang = toCheck.profile!.targetLanguage;
|
||||
final String? srcLang = profile.userSettings.sourceLanguage;
|
||||
final String? tgtLang = profile.userSettings.targetLanguage;
|
||||
return srcLang != null &&
|
||||
tgtLang != null &&
|
||||
srcLang.isNotEmpty &&
|
||||
tgtLang.isNotEmpty &&
|
||||
srcLang != LanguageKeys.unknownLanguage &&
|
||||
tgtLang != LanguageKeys.unknownLanguage;
|
||||
} catch (err) {
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(e: err, s: s);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
String? get _matrixAccessToken =>
|
||||
_pangeaController.matrixState.client.accessToken;
|
||||
|
||||
bool get isPublic =>
|
||||
_pangeaController.userController.userModel?.profile?.publicProfile ??
|
||||
false;
|
||||
/// Returns a boolean value indicating whether the user's profile is public.
|
||||
bool get isPublic {
|
||||
return profile.userSettings.publicProfile;
|
||||
}
|
||||
|
||||
/// Retrieves the user's email address.
|
||||
///
|
||||
/// This method fetches the user's email address by making a request to the
|
||||
/// Matrix server. It uses the `_pangeaController` instance to access the
|
||||
/// Matrix client and retrieve the account's third-party identifiers. It then
|
||||
/// filters the identifiers to find the first one with the medium set to
|
||||
/// `ThirdPartyIdentifierMedium.email`. Finally, it returns the email address
|
||||
/// associated with the identifier, or `null` if no email address is found.
|
||||
///
|
||||
/// Returns:
|
||||
/// - The user's email address as a [String], or `null` if no email address
|
||||
/// is found.
|
||||
Future<String?> get userEmail async {
|
||||
final List<matrix.ThirdPartyIdentifier>? identifiers =
|
||||
await _pangeaController.matrixState.client.getAccount3PIDs();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:fluffychat/pangea/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/repo/word_repo.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../models/word_data_model.dart';
|
||||
import 'base_controller.dart';
|
||||
import 'pangea_controller.dart';
|
||||
|
|
|
|||
13
lib/pangea/enum/activity_display_instructions_enum.dart
Normal file
13
lib/pangea/enum/activity_display_instructions_enum.dart
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
enum ActivityDisplayInstructionsEnum { highlight, hide }
|
||||
|
||||
extension ActivityDisplayInstructionsEnumExt
|
||||
on ActivityDisplayInstructionsEnum {
|
||||
String get string {
|
||||
switch (this) {
|
||||
case ActivityDisplayInstructionsEnum.highlight:
|
||||
return 'highlight';
|
||||
case ActivityDisplayInstructionsEnum.hide:
|
||||
return 'hide';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
|
|
@ -7,10 +8,9 @@ enum InstructionsEnum {
|
|||
clickMessage,
|
||||
blurMeansTranslate,
|
||||
tooltipInstructions,
|
||||
speechToText,
|
||||
}
|
||||
|
||||
extension Copy on InstructionsEnum {
|
||||
extension InstructionsEnumExtension on InstructionsEnum {
|
||||
String title(BuildContext context) {
|
||||
switch (this) {
|
||||
case InstructionsEnum.itInstructions:
|
||||
|
|
@ -21,8 +21,6 @@ extension Copy on InstructionsEnum {
|
|||
return L10n.of(context)!.blurMeansTranslateTitle;
|
||||
case InstructionsEnum.tooltipInstructions:
|
||||
return L10n.of(context)!.tooltipInstructionsTitle;
|
||||
case InstructionsEnum.speechToText:
|
||||
return L10n.of(context)!.hintTitle;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -34,12 +32,41 @@ extension Copy on InstructionsEnum {
|
|||
return L10n.of(context)!.clickMessageBody;
|
||||
case InstructionsEnum.blurMeansTranslate:
|
||||
return L10n.of(context)!.blurMeansTranslateBody;
|
||||
case InstructionsEnum.speechToText:
|
||||
return L10n.of(context)!.speechToTextBody;
|
||||
case InstructionsEnum.tooltipInstructions:
|
||||
return PlatformInfos.isMobile
|
||||
? L10n.of(context)!.tooltipInstructionsMobileBody
|
||||
: L10n.of(context)!.tooltipInstructionsBrowserBody;
|
||||
}
|
||||
}
|
||||
|
||||
bool get toggledOff {
|
||||
final instructionSettings =
|
||||
MatrixState.pangeaController.userController.profile.instructionSettings;
|
||||
switch (this) {
|
||||
case InstructionsEnum.itInstructions:
|
||||
return instructionSettings.showedItInstructions;
|
||||
case InstructionsEnum.clickMessage:
|
||||
return instructionSettings.showedClickMessage;
|
||||
case InstructionsEnum.blurMeansTranslate:
|
||||
return instructionSettings.showedBlurMeansTranslate;
|
||||
case InstructionsEnum.tooltipInstructions:
|
||||
return instructionSettings.showedTooltipInstructions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum InlineInstructions {
|
||||
speechToText,
|
||||
l1Translation,
|
||||
}
|
||||
|
||||
extension InlineInstructionsExtension on InlineInstructions {
|
||||
String body(BuildContext context) {
|
||||
switch (this) {
|
||||
case InlineInstructions.speechToText:
|
||||
return L10n.of(context)!.speechToTextBody;
|
||||
case InlineInstructions.l1Translation:
|
||||
return L10n.of(context)!.l1TranslationBody;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import 'package:fluffychat/pangea/models/space_model.dart';
|
|||
import 'package:fluffychat/pangea/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
part "client_analytics_extension.dart";
|
||||
|
|
@ -76,4 +77,9 @@ extension PangeaClient on Client {
|
|||
String eventId,
|
||||
) async =>
|
||||
await _getEditHistory(roomId, eventId);
|
||||
|
||||
String? powerLevelName(int powerLevel, L10n l10n) =>
|
||||
_powerLevelName(powerLevel, l10n);
|
||||
|
||||
Future<void> waitForAccountData() async => await _waitForAccountData();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,4 +72,17 @@ extension GeneralInfoClientExtension on Client {
|
|||
editEvents.add(originalEvent);
|
||||
return editEvents.slice(1).map((e) => e.eventId).toList();
|
||||
}
|
||||
|
||||
String? _powerLevelName(int powerLevel, L10n l10n) => {
|
||||
0: l10n.user,
|
||||
50: l10n.moderator,
|
||||
100: l10n.admin,
|
||||
}[powerLevel];
|
||||
|
||||
/// Account data comes through in the first sync, so wait for that
|
||||
Future<void> _waitForAccountData() async {
|
||||
if (prevBatch == null) {
|
||||
await onSync.stream.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,4 +138,27 @@ extension ChildrenAndParentsRoomExtension on Room {
|
|||
child != null ? child._allSpaceChildRoomIds.contains(id) : false;
|
||||
return canAddChild && !isCycle;
|
||||
}
|
||||
|
||||
/// Wrapper around call to setSpaceChild with added functionality
|
||||
/// to prevent adding one room to multiple spaces
|
||||
Future<void> _pangeaSetSpaceChild(
|
||||
String roomId, {
|
||||
bool? suggested,
|
||||
}) async {
|
||||
final Room? child = client.getRoomById(roomId);
|
||||
if (child != null) {
|
||||
final List<Room> spaceParents = child.pangeaSpaceParents;
|
||||
for (final Room parent in spaceParents) {
|
||||
try {
|
||||
await parent.removeSpaceChild(roomId);
|
||||
} catch (e) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
m: 'Failed to remove child from parent',
|
||||
);
|
||||
}
|
||||
}
|
||||
await setSpaceChild(roomId, suggested: suggested);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,6 +116,12 @@ extension PangeaRoom on Room {
|
|||
return _canAddAsParentOf(child, spaceMode: spaceMode);
|
||||
}
|
||||
|
||||
Future<void> pangeaSetSpaceChild(
|
||||
String roomId, {
|
||||
bool? suggested,
|
||||
}) async =>
|
||||
await _pangeaSetSpaceChild(roomId, suggested: suggested);
|
||||
|
||||
// class_and_exchange_settings
|
||||
|
||||
DateTime? get rulesUpdatedAt => _rulesUpdatedAt;
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ extension RoomSettingsRoomExtension on Room {
|
|||
|
||||
Future<void> _setSuggestedInSpace(bool suggest, Room space) async {
|
||||
try {
|
||||
await space.setSpaceChild(id, suggested: suggest);
|
||||
await space.pangeaSetSpaceChild(id, suggested: suggest);
|
||||
} catch (err) {
|
||||
ErrorHandler.logError(
|
||||
e: "Failed to set suggestion status of room $id in space ${space.id}",
|
||||
|
|
|
|||
|
|
@ -82,10 +82,9 @@ class PangeaMessageEvent {
|
|||
.firstOrNull ??
|
||||
_event;
|
||||
|
||||
Event updateLatestEdit() {
|
||||
void updateLatestEdit() {
|
||||
_latestEditCache = null;
|
||||
_representations = null;
|
||||
return _latestEdit;
|
||||
}
|
||||
|
||||
Future<PangeaAudioFile> getMatrixAudioFile(
|
||||
|
|
@ -687,7 +686,8 @@ class PangeaMessageEvent {
|
|||
|
||||
for (final itStep in originalSent!.choreo!.itSteps) {
|
||||
for (final continuance in itStep.continuances) {
|
||||
// this seems to always be false for continuances right now
|
||||
final List<PangeaToken> tokensToSave =
|
||||
continuance.tokens.where((t) => t.lemma.saveVocab).toList();
|
||||
|
||||
if (originalSent!.choreo!.finalMessage.contains(continuance.text)) {
|
||||
continue;
|
||||
|
|
@ -695,21 +695,25 @@ class PangeaMessageEvent {
|
|||
if (continuance.wasClicked) {
|
||||
//PTODO - account for end of flow score
|
||||
if (continuance.level != ChoreoConstants.levelThresholdForGreen) {
|
||||
uses.addAll(
|
||||
_lemmasToVocabUses(
|
||||
continuance.lemmas,
|
||||
ConstructUseTypeEnum.incIt,
|
||||
),
|
||||
);
|
||||
for (final token in tokensToSave) {
|
||||
uses.add(
|
||||
_lemmaToVocabUse(
|
||||
token.lemma,
|
||||
ConstructUseTypeEnum.incIt,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (continuance.level != ChoreoConstants.levelThresholdForGreen) {
|
||||
uses.addAll(
|
||||
_lemmasToVocabUses(
|
||||
continuance.lemmas,
|
||||
ConstructUseTypeEnum.ignIt,
|
||||
),
|
||||
);
|
||||
for (final token in tokensToSave) {
|
||||
uses.add(
|
||||
_lemmaToVocabUse(
|
||||
token.lemma,
|
||||
ConstructUseTypeEnum.ignIt,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -728,14 +732,16 @@ class PangeaMessageEvent {
|
|||
}
|
||||
|
||||
// for each token, record whether selected in ga, ta, or wa
|
||||
for (final token in originalSent!.tokens!) {
|
||||
uses.addAll(_getVocabUseForToken(token));
|
||||
for (final token in originalSent!.tokens!
|
||||
.where((token) => token.lemma.saveVocab)
|
||||
.toList()) {
|
||||
uses.add(_getVocabUseForToken(token));
|
||||
}
|
||||
|
||||
return uses;
|
||||
}
|
||||
|
||||
/// Returns a list of [OneConstructUse] objects for the given [token]
|
||||
/// Returns a [OneConstructUse] for the given [token]
|
||||
/// If there is no [originalSent] or [originalSent.choreo], the [token] is
|
||||
/// considered to be a [ConstructUseTypeEnum.wa] as long as it matches the target language.
|
||||
/// Later on, we may want to consider putting it in some category of like 'pending'
|
||||
|
|
@ -744,11 +750,11 @@ class PangeaMessageEvent {
|
|||
/// If the [token] is in the [originalSent.choreo.acceptedOrIgnoredMatch.choices],
|
||||
/// it is considered to be a [ConstructUseTypeEnum.corIt].
|
||||
/// If the [token] is not included in any choreoStep, it is considered to be a [ConstructUseTypeEnum.wa].
|
||||
List<OneConstructUse> _getVocabUseForToken(PangeaToken token) {
|
||||
OneConstructUse _getVocabUseForToken(PangeaToken token) {
|
||||
if (originalSent?.choreo == null) {
|
||||
final bool inUserL2 = originalSent?.langCode == l2Code;
|
||||
return _lemmasToVocabUses(
|
||||
token.lemmas,
|
||||
return _lemmaToVocabUse(
|
||||
token.lemma,
|
||||
inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk,
|
||||
);
|
||||
}
|
||||
|
|
@ -763,45 +769,34 @@ class PangeaMessageEvent {
|
|||
step.text.contains(r.value),
|
||||
) ??
|
||||
false)) {
|
||||
return _lemmasToVocabUses(token.lemmas, ConstructUseTypeEnum.ga);
|
||||
return _lemmaToVocabUse(token.lemma, ConstructUseTypeEnum.ga);
|
||||
}
|
||||
if (step.itStep != null) {
|
||||
final bool pickedThroughIT =
|
||||
step.itStep!.chosenContinuance?.text.contains(token.text.content) ??
|
||||
false;
|
||||
if (pickedThroughIT) {
|
||||
return _lemmasToVocabUses(token.lemmas, ConstructUseTypeEnum.corIt);
|
||||
return _lemmaToVocabUse(token.lemma, ConstructUseTypeEnum.corIt);
|
||||
//PTODO - check if added via custom input in IT flow
|
||||
}
|
||||
}
|
||||
}
|
||||
return _lemmasToVocabUses(token.lemmas, ConstructUseTypeEnum.wa);
|
||||
return _lemmaToVocabUse(token.lemma, ConstructUseTypeEnum.wa);
|
||||
}
|
||||
|
||||
/// Convert a list of [lemmas] into a list of vocab uses
|
||||
/// with the given [type]
|
||||
List<OneConstructUse> _lemmasToVocabUses(
|
||||
List<Lemma> lemmas,
|
||||
OneConstructUse _lemmaToVocabUse(
|
||||
Lemma lemma,
|
||||
ConstructUseTypeEnum type,
|
||||
) {
|
||||
final List<OneConstructUse> uses = [];
|
||||
for (final lemma in lemmas) {
|
||||
if (lemma.saveVocab) {
|
||||
uses.add(
|
||||
OneConstructUse(
|
||||
useType: type,
|
||||
chatId: event.roomId!,
|
||||
timeStamp: event.originServerTs,
|
||||
lemma: lemma.text,
|
||||
form: lemma.form,
|
||||
msgId: event.eventId,
|
||||
constructType: ConstructTypeEnum.vocab,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return uses;
|
||||
}
|
||||
) =>
|
||||
OneConstructUse(
|
||||
useType: type,
|
||||
chatId: event.roomId!,
|
||||
timeStamp: event.originServerTs,
|
||||
lemma: lemma.text,
|
||||
form: lemma.form,
|
||||
msgId: event.eventId,
|
||||
constructType: ConstructTypeEnum.vocab,
|
||||
);
|
||||
|
||||
/// get construct uses of type grammar for the message
|
||||
List<OneConstructUse> get _grammarConstructUses {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
|
||||
import 'package:fluffychat/pangea/repo/subscription_repo.dart';
|
||||
|
|
@ -51,6 +52,11 @@ class SubscriptionInfo {
|
|||
bool get currentSubscriptionIsPromotional =>
|
||||
currentSubscriptionId?.startsWith("rc_promo") ?? false;
|
||||
|
||||
bool get isPaidSubscription =>
|
||||
(currentSubscription != null || currentSubscriptionId != null) &&
|
||||
!isNewUserTrial &&
|
||||
!currentSubscriptionIsPromotional;
|
||||
|
||||
bool get isLifetimeSubscription =>
|
||||
currentSubscriptionIsPromotional &&
|
||||
expirationDate != null &&
|
||||
|
|
@ -86,4 +92,13 @@ class SubscriptionInfo {
|
|||
}
|
||||
|
||||
Future<void> setCustomerInfo() async {}
|
||||
|
||||
String? get defaultManagementURL {
|
||||
final String? purchaseAppId = currentSubscription?.appId;
|
||||
return purchaseAppId == appIds?.androidId
|
||||
? AppConfig.googlePlayMangementUrl
|
||||
: purchaseAppId == appIds?.appleId
|
||||
? AppConfig.appleMangementUrl
|
||||
: Environment.stripeManagementUrl;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fluffychat/pangea/controllers/language_detection_controller.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_match_model.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/models/span_card_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/utils/overlay.dart';
|
||||
import 'package:fluffychat/pangea/widgets/igc/span_card.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
|
@ -262,7 +260,20 @@ class IGCTextData {
|
|||
return matchTokens;
|
||||
}
|
||||
|
||||
TextSpan getSpanItem({
|
||||
required int start,
|
||||
required int end,
|
||||
TextStyle? style,
|
||||
}) {
|
||||
return TextSpan(
|
||||
text: originalInput.characters.getRange(start, end).toString(),
|
||||
style: style,
|
||||
);
|
||||
}
|
||||
|
||||
//PTODO - handle multitoken spans
|
||||
/// Returns a list of [TextSpan]s used to display the text in the input field
|
||||
/// with the appropriate styling for each error match.
|
||||
List<TextSpan> constructTokenSpan({
|
||||
required BuildContext context,
|
||||
TextStyle? defaultStyle,
|
||||
|
|
@ -282,79 +293,58 @@ class IGCTextData {
|
|||
];
|
||||
}
|
||||
|
||||
final List<MatchToken> matchTokens = getMatchTokens();
|
||||
final List<List<int>> matchRanges = matches
|
||||
.map(
|
||||
(match) => [
|
||||
match.match.offset,
|
||||
match.match.length + match.match.offset,
|
||||
],
|
||||
)
|
||||
.toList();
|
||||
|
||||
for (int tokenIndex = 0; tokenIndex < matchTokens.length; tokenIndex++) {
|
||||
final MatchToken matchToken = matchTokens[tokenIndex];
|
||||
final Widget? cardToShow =
|
||||
matchToken.match != null && spanCardModel != null
|
||||
? SpanCard(scm: spanCardModel)
|
||||
: null;
|
||||
|
||||
int nextTokenIndex = matchTokens.indexWhere(
|
||||
(e) => matchToken.match != null
|
||||
? e.match != matchToken.match
|
||||
: e.match != null,
|
||||
tokenIndex,
|
||||
// create a pointer to the current index in the original input
|
||||
// and iterate until the pointer has reached the end of the input
|
||||
int currentIndex = 0;
|
||||
while (currentIndex < originalInput.characters.length - 1) {
|
||||
// check if the pointer is at a match, and if so, get the index of the match
|
||||
final int matchIndex = matchRanges.indexWhere(
|
||||
(range) => currentIndex >= range[0] && currentIndex < range[1],
|
||||
);
|
||||
final bool inMatch = matchIndex != -1;
|
||||
|
||||
if (nextTokenIndex < 0) {
|
||||
nextTokenIndex = matchTokens.length;
|
||||
}
|
||||
|
||||
String matchText;
|
||||
try {
|
||||
final int start = matchTokens[tokenIndex].token.text.offset;
|
||||
final int end = matchTokens[nextTokenIndex - 1].token.end;
|
||||
matchText = originalInput.characters.getRange(start, end).toString();
|
||||
} catch (err) {
|
||||
return [
|
||||
TextSpan(
|
||||
text: originalInput,
|
||||
style: defaultStyle,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
items.add(
|
||||
TextSpan(
|
||||
text: matchText,
|
||||
style: matchTokens[tokenIndex].match?.textStyle(defaultStyle) ??
|
||||
defaultStyle,
|
||||
recognizer: handleClick && cardToShow != null
|
||||
? (TapGestureRecognizer()
|
||||
..onTapDown = (details) => OverlayUtil.showPositionedCard(
|
||||
context: context,
|
||||
cardToShow: cardToShow,
|
||||
cardSize:
|
||||
matchTokens[tokenIndex].match?.isITStart ?? false
|
||||
? const Size(350, 220)
|
||||
: const Size(350, 400),
|
||||
transformTargetId: transformTargetId,
|
||||
))
|
||||
: null,
|
||||
),
|
||||
);
|
||||
|
||||
final String beforeNextToken = originalInput.characters
|
||||
.getRange(
|
||||
matchTokens[nextTokenIndex - 1].token.end,
|
||||
nextTokenIndex < matchTokens.length
|
||||
? matchTokens[nextTokenIndex].token.text.offset
|
||||
: originalInput.length,
|
||||
)
|
||||
.toString();
|
||||
|
||||
if (beforeNextToken.isNotEmpty) {
|
||||
if (inMatch) {
|
||||
// if the pointer is in a match, then add that match to items
|
||||
// and then move the pointer to the end of the match range
|
||||
final PangeaMatch match = matches[matchIndex];
|
||||
items.add(
|
||||
TextSpan(
|
||||
text: beforeNextToken,
|
||||
getSpanItem(
|
||||
start: match.match.offset,
|
||||
end: match.match.offset + match.match.length,
|
||||
style: match.textStyle(defaultStyle),
|
||||
),
|
||||
);
|
||||
|
||||
currentIndex = match.match.offset + match.match.length;
|
||||
} else {
|
||||
// otherwise, if the pointer is not at a match, then add all the text
|
||||
// until the next match (or, if there is not next match, the end of the
|
||||
// text) to items and move the pointer to the start of the next match
|
||||
final int nextIndex = matchRanges
|
||||
.firstWhereOrNull(
|
||||
(range) => range[0] > currentIndex,
|
||||
)
|
||||
?.first ??
|
||||
originalInput.characters.length;
|
||||
|
||||
items.add(
|
||||
getSpanItem(
|
||||
start: currentIndex,
|
||||
end: nextIndex,
|
||||
style: defaultStyle,
|
||||
),
|
||||
);
|
||||
currentIndex = nextIndex;
|
||||
}
|
||||
|
||||
tokenIndex = nextTokenIndex - 1;
|
||||
}
|
||||
|
||||
return items;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@ import 'package:collection/collection.dart';
|
|||
import 'package:fluffychat/pangea/constants/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/extensions/my_list_extension.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'lemma.dart';
|
||||
|
||||
class ITResponseModel {
|
||||
String fullTextTranslation;
|
||||
List<Continuance> continuances;
|
||||
|
|
@ -79,7 +78,7 @@ class Continuance {
|
|||
double probability;
|
||||
int level;
|
||||
String text;
|
||||
List<Lemma> lemmas;
|
||||
List<PangeaToken> tokens;
|
||||
|
||||
/// saving this in a full json form
|
||||
String description;
|
||||
|
|
@ -99,19 +98,18 @@ class Continuance {
|
|||
required this.inDictionary,
|
||||
required this.hasInfo,
|
||||
required this.gold,
|
||||
required this.lemmas,
|
||||
required this.tokens,
|
||||
});
|
||||
|
||||
factory Continuance.fromJson(Map<String, dynamic> json) {
|
||||
final List<Lemma> lemmaInternal =
|
||||
(json[ModelKey.lemma] != null && json[ModelKey.lemma] is Iterable)
|
||||
? (json[ModelKey.lemma] as Iterable)
|
||||
.map<Lemma>(
|
||||
(e) => Lemma.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList()
|
||||
.cast<Lemma>()
|
||||
: [];
|
||||
final List<PangeaToken> tokensInternal = (json[ModelKey.tokens] != null)
|
||||
? (json[ModelKey.tokens] as Iterable)
|
||||
.map<PangeaToken>(
|
||||
(e) => PangeaToken.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList()
|
||||
.cast<PangeaToken>()
|
||||
: [];
|
||||
return Continuance(
|
||||
probability: json['probability'].toDouble(),
|
||||
level: json['level'],
|
||||
|
|
@ -122,7 +120,7 @@ class Continuance {
|
|||
wasClicked: json['clkd'] ?? false,
|
||||
hasInfo: json['has_info'] ?? false,
|
||||
gold: json['gold'] ?? false,
|
||||
lemmas: lemmaInternal,
|
||||
tokens: tokensInternal,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -132,7 +130,7 @@ class Continuance {
|
|||
data['level'] = level;
|
||||
data['text'] = text;
|
||||
data['clkd'] = wasClicked;
|
||||
data[ModelKey.lemma] = lemmas.map((e) => e.toJson()).toList();
|
||||
data[ModelKey.tokens] = tokens.map((e) => e.toJson()).toList();
|
||||
|
||||
if (!condensed) {
|
||||
data['description'] = description;
|
||||
|
|
|
|||
|
|
@ -8,22 +8,13 @@ class Lemma {
|
|||
|
||||
/// [saveVocab] true - whether to save the lemma to the user's vocabulary
|
||||
/// vocab that are not saved: emails, urls, numbers, punctuation, etc.
|
||||
/// server handles this determination
|
||||
final bool saveVocab;
|
||||
|
||||
/// [pos] ex "v" - part of speech of the lemma
|
||||
/// https://universaldependencies.org/u/pos/
|
||||
final String pos;
|
||||
|
||||
/// [morph] ex {} - morphological features of the lemma
|
||||
/// https://universaldependencies.org/u/feat/
|
||||
final Map<String, dynamic> morph;
|
||||
|
||||
Lemma({
|
||||
required this.text,
|
||||
required this.saveVocab,
|
||||
required this.form,
|
||||
this.pos = '',
|
||||
this.morph = const {},
|
||||
});
|
||||
|
||||
factory Lemma.fromJson(Map<String, dynamic> json) {
|
||||
|
|
@ -31,8 +22,6 @@ class Lemma {
|
|||
text: json['text'],
|
||||
saveVocab: json['save_vocab'] ?? json['saveVocab'] ?? false,
|
||||
form: json["form"] ?? json['text'],
|
||||
pos: json['pos'] ?? '',
|
||||
morph: json['morph'] ?? {},
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -41,8 +30,6 @@ class Lemma {
|
|||
'text': text,
|
||||
'save_vocab': saveVocab,
|
||||
'form': form,
|
||||
'pos': pos,
|
||||
'morph': morph,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,55 +1,60 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import '../constants/model_keys.dart';
|
||||
import '../utils/error_handler.dart';
|
||||
import 'lemma.dart';
|
||||
|
||||
class PangeaToken {
|
||||
PangeaTokenText text;
|
||||
List<Lemma> lemmas;
|
||||
Lemma lemma;
|
||||
|
||||
/// [pos] ex "VERB" - part of speech of the token
|
||||
/// https://universaldependencies.org/u/pos/
|
||||
final String pos;
|
||||
|
||||
/// [morph] ex {} - morphological features of the token
|
||||
/// https://universaldependencies.org/u/feat/
|
||||
final Map<String, dynamic> morph;
|
||||
|
||||
PangeaToken({
|
||||
required this.text,
|
||||
required this.lemmas,
|
||||
required this.lemma,
|
||||
required this.pos,
|
||||
required this.morph,
|
||||
});
|
||||
|
||||
static getLemmas(String text, Iterable? json) {
|
||||
static Lemma _getLemmas(String text, dynamic json) {
|
||||
if (json != null) {
|
||||
return json
|
||||
.map<Lemma>(
|
||||
(e) => Lemma.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList()
|
||||
.cast<Lemma>();
|
||||
// July 24, 2024 - we're changing from a list to a single lemma and this is for backwards compatibility
|
||||
// previously sent tokens have lists of lemmas
|
||||
if (json is Iterable) {
|
||||
return json
|
||||
.map<Lemma>(
|
||||
(e) => Lemma.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList()
|
||||
.cast<Lemma>()
|
||||
.firstOrNull ??
|
||||
Lemma(text: text, saveVocab: false, form: text);
|
||||
} else {
|
||||
return Lemma.fromJson(json);
|
||||
}
|
||||
} else {
|
||||
return [Lemma(text: text, saveVocab: false, form: text)];
|
||||
// earlier still, we didn't have lemmas so this is for really old tokens
|
||||
return Lemma(text: text, saveVocab: false, form: text);
|
||||
}
|
||||
}
|
||||
|
||||
factory PangeaToken.fromJson(Map<String, dynamic> json) {
|
||||
try {
|
||||
final PangeaTokenText text =
|
||||
PangeaTokenText.fromJson(json[_textKey] as Map<String, dynamic>);
|
||||
return PangeaToken(
|
||||
text: text,
|
||||
lemmas: getLemmas(text.content, json[_lemmaKey]),
|
||||
);
|
||||
} catch (err, s) {
|
||||
debugger(when: kDebugMode);
|
||||
Sentry.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message: "PangeaToken.fromJson error",
|
||||
data: {
|
||||
"json": json,
|
||||
},
|
||||
),
|
||||
);
|
||||
ErrorHandler.logError(e: err, s: s);
|
||||
rethrow;
|
||||
}
|
||||
final PangeaTokenText text =
|
||||
PangeaTokenText.fromJson(json[_textKey] as Map<String, dynamic>);
|
||||
return PangeaToken(
|
||||
text: text,
|
||||
lemma: _getLemmas(text.content, json[_lemmaKey]),
|
||||
pos: json['pos'] ?? '',
|
||||
morph: json['morph'] ?? {},
|
||||
);
|
||||
}
|
||||
|
||||
static const String _textKey = "text";
|
||||
|
|
@ -57,7 +62,9 @@ class PangeaToken {
|
|||
|
||||
Map<String, dynamic> toJson() => {
|
||||
_textKey: text.toJson(),
|
||||
_lemmaKey: lemmas.map((e) => e.toJson()).toList(),
|
||||
_lemmaKey: [lemma.toJson()],
|
||||
'pos': pos,
|
||||
'morph': morph,
|
||||
};
|
||||
|
||||
int get end => text.offset + text.length;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MultipleChoice {
|
||||
final String question;
|
||||
final List<String> choices;
|
||||
final String answer;
|
||||
final RelevantSpanDisplayDetails? spanDisplayDetails;
|
||||
|
||||
MultipleChoice({
|
||||
required this.question,
|
||||
required this.choices,
|
||||
required this.answer,
|
||||
this.spanDisplayDetails,
|
||||
});
|
||||
|
||||
bool isCorrect(int index) => index == correctAnswerIndex;
|
||||
|
|
@ -28,6 +31,9 @@ class MultipleChoice {
|
|||
question: json['question'] as String,
|
||||
choices: (json['choices'] as List).map((e) => e as String).toList(),
|
||||
answer: json['answer'] ?? json['correct_answer'] as String,
|
||||
spanDisplayDetails: json['span_display_details'] != null
|
||||
? RelevantSpanDisplayDetails.fromJson(json['span_display_details'])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -36,6 +42,7 @@ class MultipleChoice {
|
|||
'question': question,
|
||||
'choices': choices,
|
||||
'answer': answer,
|
||||
'span_display_details': spanDisplayDetails,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fluffychat/pangea/enum/activity_display_instructions_enum.dart';
|
||||
import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/models/practice_activities.dart/multiple_choice_activity_model.dart';
|
||||
|
|
@ -279,4 +281,58 @@ class PracticeActivityModel {
|
|||
'free_response': freeResponse?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
RelevantSpanDisplayDetails? getRelevantSpanDisplayDetails() {
|
||||
switch (activityType) {
|
||||
case ActivityTypeEnum.multipleChoice:
|
||||
return multipleChoice?.spanDisplayDetails;
|
||||
case ActivityTypeEnum.listening:
|
||||
return null;
|
||||
case ActivityTypeEnum.speaking:
|
||||
return null;
|
||||
case ActivityTypeEnum.freeResponse:
|
||||
return null;
|
||||
default:
|
||||
debugger(when: kDebugMode);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// For those activities with a relevant span, this class will hold the details
|
||||
/// of the span and how it should be displayed
|
||||
/// e.g. hide the span for conjugation activities
|
||||
class RelevantSpanDisplayDetails {
|
||||
final int offset;
|
||||
final int length;
|
||||
final ActivityDisplayInstructionsEnum displayInstructions;
|
||||
|
||||
RelevantSpanDisplayDetails({
|
||||
required this.offset,
|
||||
required this.length,
|
||||
required this.displayInstructions,
|
||||
});
|
||||
|
||||
factory RelevantSpanDisplayDetails.fromJson(Map<String, dynamic> json) {
|
||||
final ActivityDisplayInstructionsEnum? display =
|
||||
ActivityDisplayInstructionsEnum.values.firstWhereOrNull(
|
||||
(e) => e.string == json['display_instructions'],
|
||||
);
|
||||
if (display == null) {
|
||||
debugger(when: kDebugMode);
|
||||
}
|
||||
return RelevantSpanDisplayDetails(
|
||||
offset: json['offset'] as int,
|
||||
length: json['length'] as int,
|
||||
displayInstructions: display ?? ActivityDisplayInstructionsEnum.hide,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'offset': offset,
|
||||
'length': length,
|
||||
'display_instructions': displayInstructions,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,46 +109,6 @@ class PangeaRoomRules {
|
|||
this.autoIGC = ClassDefaultValues.languageToolPermissions,
|
||||
});
|
||||
|
||||
updatePermission(String key, bool value) {
|
||||
switch (key) {
|
||||
case 'isPublic':
|
||||
isPublic = value;
|
||||
break;
|
||||
case 'isOpenEnrollment':
|
||||
isOpenEnrollment = value;
|
||||
break;
|
||||
case 'oneToOneChatClass':
|
||||
oneToOneChatClass = value;
|
||||
break;
|
||||
case 'isCreateRooms':
|
||||
isCreateRooms = value;
|
||||
break;
|
||||
case 'isShareVideo':
|
||||
isShareVideo = value;
|
||||
break;
|
||||
case 'isSharePhoto':
|
||||
isSharePhoto = value;
|
||||
break;
|
||||
case 'isShareFiles':
|
||||
isShareFiles = value;
|
||||
break;
|
||||
case 'isShareLocation':
|
||||
isShareLocation = value;
|
||||
break;
|
||||
case 'isCreateStories':
|
||||
isCreateStories = value;
|
||||
break;
|
||||
case 'isVoiceNotes':
|
||||
isVoiceNotes = value;
|
||||
break;
|
||||
case 'isInviteOnlyStudents':
|
||||
isInviteOnlyStudents = value;
|
||||
break;
|
||||
default:
|
||||
throw Exception('Invalid key for setting permissions - $key');
|
||||
}
|
||||
}
|
||||
|
||||
setLanguageToolSetting(ToolSetting setting, int value) {
|
||||
switch (setting) {
|
||||
case ToolSetting.interactiveTranslator:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -4,7 +4,7 @@ class UserProfileSearchResponse {
|
|||
int count;
|
||||
String? next;
|
||||
String? previous;
|
||||
List<Profile> results;
|
||||
List<PangeaProfile> results;
|
||||
|
||||
UserProfileSearchResponse({
|
||||
required this.count,
|
||||
|
|
@ -19,9 +19,9 @@ class UserProfileSearchResponse {
|
|||
next: json["next"],
|
||||
previous: json["previous"],
|
||||
results: json["results"]
|
||||
.map((p) => Profile.fromJson(p))
|
||||
.map((p) => PangeaProfile.fromJson(p))
|
||||
.toList()
|
||||
.cast<Profile>(),
|
||||
.cast<PangeaProfile>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
class MeasurableWidget extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Function(Size? size, Offset? position) onChange;
|
||||
|
||||
const MeasurableWidget({
|
||||
super.key,
|
||||
required this.onChange,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
@override
|
||||
_WidgetSizeState createState() => _WidgetSizeState();
|
||||
}
|
||||
|
||||
class _WidgetSizeState extends State<MeasurableWidget> {
|
||||
var widgetKey = GlobalKey();
|
||||
Offset? oldPosition;
|
||||
@override
|
||||
void initState() {
|
||||
// TODO: implement initState
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void postFrameCallback(_) {
|
||||
final context = widgetKey.currentContext;
|
||||
if (context == null) return;
|
||||
|
||||
final RenderBox? box =
|
||||
widgetKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
|
||||
if (box != null && box.hasSize) {
|
||||
final Offset position = box.localToGlobal(Offset.zero);
|
||||
|
||||
if (oldPosition != null) {
|
||||
if (oldPosition!.dx == position.dx && oldPosition!.dy == position.dy) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
oldPosition = position;
|
||||
|
||||
final newSize = context.size;
|
||||
widget.onChange(newSize, position);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SchedulerBinding.instance.addPostFrameCallback(postFrameCallback);
|
||||
return Container(
|
||||
key: widgetKey,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,9 @@ import 'dart:async';
|
|||
import 'package:country_picker/country_picker.dart';
|
||||
import 'package:fluffychat/pangea/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/models/user_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import '../../../widgets/matrix.dart';
|
||||
import '../../controllers/pangea_controller.dart';
|
||||
|
|
@ -35,9 +37,10 @@ class FindPartnerController extends State<FindPartner> {
|
|||
|
||||
Timer? coolDown;
|
||||
|
||||
final List<Profile> _userProfilesCache = [];
|
||||
final List<PangeaProfile> _userProfilesCache = [];
|
||||
|
||||
final scrollController = ScrollController();
|
||||
String? error;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -66,10 +69,21 @@ class FindPartnerController extends State<FindPartner> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (error != null && error!.isNotEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(L10n.of(context)!.oopsSomethingWentWrong),
|
||||
Text(L10n.of(context)!.errorPleaseRefresh),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return FindPartnerView(this);
|
||||
}
|
||||
|
||||
List<Profile> get userProfiles => _userProfilesCache.where((p) {
|
||||
List<PangeaProfile> get userProfiles => _userProfilesCache.where((p) {
|
||||
return (p.targetLanguage != null &&
|
||||
targetLanguageSearch.langCode == p.targetLanguage) &&
|
||||
(p.sourceLanguage != null &&
|
||||
|
|
@ -91,21 +105,29 @@ class FindPartnerController extends State<FindPartner> {
|
|||
if (loading || nextUrl == null) return;
|
||||
setState(() => loading = true);
|
||||
|
||||
final UserProfileSearchResponse response =
|
||||
await PUserRepo.searchUserProfiles(
|
||||
accessToken: await pangeaController.userController.accessToken,
|
||||
targetLanguage: targetLanguageSearch.langCode,
|
||||
sourceLanguage: sourceLanguageSearch.langCode,
|
||||
country: countrySearch,
|
||||
limit: 15,
|
||||
pageNumber: nextPage.toString(),
|
||||
);
|
||||
UserProfileSearchResponse response;
|
||||
try {
|
||||
final String accessToken =
|
||||
await pangeaController.userController.accessToken;
|
||||
response = await PUserRepo.searchUserProfiles(
|
||||
accessToken: accessToken,
|
||||
targetLanguage: targetLanguageSearch.langCode,
|
||||
sourceLanguage: sourceLanguageSearch.langCode,
|
||||
country: countrySearch,
|
||||
limit: 15,
|
||||
pageNumber: nextPage.toString(),
|
||||
);
|
||||
} catch (err, s) {
|
||||
error = err.toString();
|
||||
setState(() => loading = false);
|
||||
ErrorHandler.logError(e: err, s: s);
|
||||
return;
|
||||
}
|
||||
|
||||
nextUrl = response.next;
|
||||
nextPage++;
|
||||
|
||||
final String? currentUserId =
|
||||
pangeaController.userController.userModel?.profile?.pangeaUserId;
|
||||
final String? currentUserId = pangeaController.matrixState.client.userID;
|
||||
_userProfilesCache.addAll(
|
||||
response.results.where(
|
||||
(p) =>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:country_picker/country_picker.dart';
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/models/user_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/country_display.dart';
|
||||
import 'package:fluffychat/pangea/widgets/common/list_placeholder.dart';
|
||||
import 'package:fluffychat/pangea/widgets/common/pangea_logo_svg.dart';
|
||||
import 'package:fluffychat/pangea/widgets/user_settings/p_language_dropdown.dart';
|
||||
|
|
@ -244,7 +245,7 @@ class LanguageSelectionRow extends StatelessWidget {
|
|||
}
|
||||
|
||||
class UserProfileEntry extends StatelessWidget {
|
||||
final Profile pangeaProfile;
|
||||
final PangeaProfile pangeaProfile;
|
||||
final FindPartnerController controller;
|
||||
|
||||
const UserProfileEntry({
|
||||
|
|
@ -287,7 +288,7 @@ class UserProfileEntry extends StatelessWidget {
|
|||
const SizedBox(width: 20),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
text: pangeaProfile.flagEmoji,
|
||||
text: CountryDisplayUtil.flagEmoji(pangeaProfile.country),
|
||||
style: const TextStyle(fontSize: 15),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../utils/bot_name.dart';
|
||||
import '../../utils/error_handler.dart';
|
||||
|
|
@ -73,26 +72,24 @@ class PUserAgeController extends State<PUserAge> {
|
|||
}
|
||||
|
||||
//Note: used linear progress bar (also used in fluffychat signup button) for consistency
|
||||
createUserInPangea() async {
|
||||
Future<void> createUserInPangea() async {
|
||||
try {
|
||||
setState(() {
|
||||
error = dobValidator();
|
||||
});
|
||||
|
||||
setState(() => error = dobValidator());
|
||||
if (error?.isNotEmpty == true) return;
|
||||
setState(() => loading = true);
|
||||
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
final DateTime? dob =
|
||||
pangeaController.userController.profile.userSettings.dateOfBirth;
|
||||
|
||||
final String date = DateFormat('yyyy-MM-dd').format(selectedDate!);
|
||||
|
||||
if (pangeaController.userController.userModel?.access == null) {
|
||||
await pangeaController.userController.createPangeaUser(dob: date);
|
||||
} else {
|
||||
await pangeaController.userController.updateUserProfile(
|
||||
dateOfBirth: date,
|
||||
if (dob == null) {
|
||||
await pangeaController.userController.createProfile(
|
||||
dob: selectedDate!,
|
||||
);
|
||||
} else {
|
||||
pangeaController.userController.updateProfile((profile) {
|
||||
profile.userSettings.dateOfBirth = selectedDate!;
|
||||
return profile;
|
||||
});
|
||||
}
|
||||
FluffyChatApp.router.go('/rooms');
|
||||
} catch (err, s) {
|
||||
|
|
|
|||
|
|
@ -17,32 +17,25 @@ class PUserAgeView extends StatelessWidget {
|
|||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: Text(
|
||||
L10n.of(context)!.yourBirthdayPlease,
|
||||
textAlign: TextAlign.justify,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer
|
||||
.withAlpha(50),
|
||||
color: Theme.of(context).colorScheme.onSecondaryContainer.withAlpha(50),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: Text(
|
||||
L10n.of(context)!.yourBirthdayPlease,
|
||||
textAlign: TextAlign.justify,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
L10n.of(context)!.certifyAge(13),
|
||||
|
|
@ -70,23 +63,16 @@ class PUserAgeView extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
if (controller.error != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
controller.error!,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Hero(
|
||||
tag: 'loginButton',
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: ElevatedButton(
|
||||
onPressed: controller.createUserInPangea,
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size.fromHeight(50),
|
||||
),
|
||||
child: controller.loading
|
||||
? const LinearProgressIndicator()
|
||||
: Text(L10n.of(context)!.getStarted),
|
||||
|
|
@ -95,7 +81,6 @@ class PUserAgeView extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
// ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:country_picker/country_picker.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/models/space_model.dart';
|
||||
import 'package:fluffychat/pangea/models/user_model.dart';
|
||||
import 'package:fluffychat/pangea/pages/settings_learning/settings_learning_view.dart';
|
||||
import 'package:fluffychat/pangea/widgets/user_settings/p_language_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -15,40 +15,59 @@ class SettingsLearning extends StatefulWidget {
|
|||
}
|
||||
|
||||
class SettingsLearningController extends State<SettingsLearning> {
|
||||
late StreamSubscription _userSubscription;
|
||||
PangeaController pangeaController = MatrixState.pangeaController;
|
||||
|
||||
setPublicProfile(bool b) async {
|
||||
await pangeaController.userController.updateUserProfile(publicProfile: b);
|
||||
setPublicProfile(bool isPublic) {
|
||||
pangeaController.userController.updateProfile((profile) {
|
||||
profile.userSettings.publicProfile = isPublic;
|
||||
return profile;
|
||||
});
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
void changeLanguage() {
|
||||
pLanguageDialog(context, () {}).then((_) => setState(() {}));
|
||||
}
|
||||
|
||||
_userSubscription =
|
||||
pangeaController.userController.stateStream.listen((event) {
|
||||
setState(() {});
|
||||
void changeCountry(Country country) {
|
||||
pangeaController.userController.updateProfile((Profile profile) {
|
||||
profile.userSettings.country = country.displayNameNoCountryCode;
|
||||
return profile;
|
||||
});
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void updateToolSetting(ToolSetting toolSetting, bool value) {
|
||||
pangeaController.userController.updateProfile((Profile profile) {
|
||||
switch (toolSetting) {
|
||||
case ToolSetting.interactiveTranslator:
|
||||
return profile..toolSettings.interactiveTranslator = value;
|
||||
case ToolSetting.interactiveGrammar:
|
||||
return profile..toolSettings.interactiveGrammar = value;
|
||||
case ToolSetting.immersionMode:
|
||||
return profile..toolSettings.immersionMode = value;
|
||||
case ToolSetting.definitions:
|
||||
return profile..toolSettings.definitions = value;
|
||||
case ToolSetting.autoIGC:
|
||||
return profile..toolSettings.autoIGC = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> changeLanguage() async {
|
||||
await pLanguageDialog(context, () {});
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Future<void> changeCountry(Country country) async {
|
||||
await pangeaController.userController.updateUserProfile(
|
||||
country: country.displayNameNoCountryCode,
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_userSubscription.cancel();
|
||||
bool getToolSetting(ToolSetting toolSetting) {
|
||||
final toolSettings = pangeaController.userController.profile.toolSettings;
|
||||
switch (toolSetting) {
|
||||
case ToolSetting.interactiveTranslator:
|
||||
return toolSettings.interactiveTranslator;
|
||||
case ToolSetting.interactiveGrammar:
|
||||
return toolSettings.interactiveGrammar;
|
||||
case ToolSetting.immersionMode:
|
||||
return toolSettings.immersionMode;
|
||||
case ToolSetting.definitions:
|
||||
return toolSettings.definitions;
|
||||
case ToolSetting.autoIGC:
|
||||
return toolSettings.autoIGC;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
import 'package:fluffychat/pangea/constants/local.key.dart';
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/models/space_model.dart';
|
||||
import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/widgets/user_settings/country_picker_tile.dart';
|
||||
import 'package:fluffychat/pangea/widgets/user_settings/language_tile.dart';
|
||||
import 'package:fluffychat/pangea/widgets/user_settings/p_settings_switch_list_tile.dart';
|
||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
|
||||
import '../../../config/app_config.dart';
|
||||
|
||||
class SettingsLearningView extends StatelessWidget {
|
||||
final SettingsLearningController controller;
|
||||
|
|
@ -42,44 +38,46 @@ class SettingsLearningView extends StatelessWidget {
|
|||
title: Text(L10n.of(context)!.publicProfileTitle),
|
||||
subtitle: Text(L10n.of(context)!.publicProfileDesc),
|
||||
value: controller.pangeaController.userController.isPublic,
|
||||
onChanged: (bool isPublicProfile) => showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => controller.setPublicProfile(isPublicProfile),
|
||||
onError: (err) =>
|
||||
ErrorHandler.logError(e: err, s: StackTrace.current),
|
||||
),
|
||||
onChanged: (bool isPublicProfile) =>
|
||||
controller.setPublicProfile(isPublicProfile),
|
||||
),
|
||||
ListTile(
|
||||
subtitle: Text(L10n.of(context)!.toggleToolSettingsDescription),
|
||||
),
|
||||
for (final setting in ToolSetting.values)
|
||||
PSettingsSwitchListTile.adaptive(
|
||||
defaultValue: controller.pangeaController.localSettings
|
||||
.userLanguageToolSetting(setting),
|
||||
title: setting.toolName(context),
|
||||
subtitle: setting.toolDescription(context),
|
||||
pStoreKey: setting.toString(),
|
||||
local: false,
|
||||
for (final toolSetting in ToolSetting.values)
|
||||
ProfileSettingsSwitchListTile.adaptive(
|
||||
defaultValue: controller.getToolSetting(toolSetting),
|
||||
title: toolSetting.toolName(context),
|
||||
subtitle: toolSetting.toolDescription(context),
|
||||
onChange: (bool value) => controller.updateToolSetting(
|
||||
toolSetting,
|
||||
value,
|
||||
),
|
||||
),
|
||||
PSettingsSwitchListTile.adaptive(
|
||||
defaultValue: controller.pangeaController.pStoreService.read(
|
||||
PLocalKey.itAutoPlay,
|
||||
) ??
|
||||
false,
|
||||
title: L10n.of(context)!.interactiveTranslatorAutoPlaySliderHeader,
|
||||
ProfileSettingsSwitchListTile.adaptive(
|
||||
defaultValue: controller.pangeaController.userController.profile
|
||||
.userSettings.itAutoPlay,
|
||||
title:
|
||||
L10n.of(context)!.interactiveTranslatorAutoPlaySliderHeader,
|
||||
subtitle: L10n.of(context)!.interactiveTranslatorAutoPlayDesc,
|
||||
pStoreKey: PLocalKey.itAutoPlay,
|
||||
local: false,
|
||||
onChange: (bool value) => controller
|
||||
.pangeaController.userController
|
||||
.updateProfile((profile) {
|
||||
profile.userSettings.itAutoPlay = value;
|
||||
return profile;
|
||||
}),
|
||||
),
|
||||
PSettingsSwitchListTile.adaptive(
|
||||
defaultValue: controller.pangeaController.pStoreService.read(
|
||||
PLocalKey.autoPlayMessages,
|
||||
) ??
|
||||
false,
|
||||
ProfileSettingsSwitchListTile.adaptive(
|
||||
defaultValue: controller.pangeaController.userController.profile
|
||||
.userSettings.autoPlayMessages,
|
||||
title: L10n.of(context)!.autoPlayTitle,
|
||||
subtitle: L10n.of(context)!.autoPlayDesc,
|
||||
pStoreKey: PLocalKey.autoPlayMessages,
|
||||
local: false,
|
||||
onChange: (bool value) => controller
|
||||
.pangeaController.userController
|
||||
.updateProfile((profile) {
|
||||
profile.userSettings.autoPlayMessages = value;
|
||||
return profile;
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -47,23 +47,33 @@ class IgcRepo {
|
|||
tokens: [
|
||||
PangeaToken(
|
||||
text: PangeaTokenText(content: "This", offset: 0, length: 4),
|
||||
lemmas: [Lemma(form: "This", text: "this", saveVocab: true)],
|
||||
lemma: Lemma(form: "This", text: "this", saveVocab: true),
|
||||
pos: "DET",
|
||||
morph: {},
|
||||
),
|
||||
PangeaToken(
|
||||
text: PangeaTokenText(content: "be", offset: 5, length: 2),
|
||||
lemmas: [Lemma(form: "be", text: "be", saveVocab: true)],
|
||||
lemma: Lemma(form: "be", text: "be", saveVocab: true),
|
||||
pos: "VERB",
|
||||
morph: {},
|
||||
),
|
||||
PangeaToken(
|
||||
text: PangeaTokenText(content: "a", offset: 8, length: 1),
|
||||
lemmas: [],
|
||||
lemma: Lemma(form: "a", text: "a", saveVocab: true),
|
||||
pos: "DET",
|
||||
morph: {},
|
||||
),
|
||||
PangeaToken(
|
||||
text: PangeaTokenText(content: "sample", offset: 10, length: 6),
|
||||
lemmas: [],
|
||||
lemma: Lemma(form: "sample", text: "sample", saveVocab: true),
|
||||
pos: "NOUN",
|
||||
morph: {},
|
||||
),
|
||||
PangeaToken(
|
||||
text: PangeaTokenText(content: "text", offset: 17, length: 4),
|
||||
lemmas: [],
|
||||
lemma: Lemma(form: "text", text: "text", saveVocab: true),
|
||||
pos: "NOUN",
|
||||
morph: {},
|
||||
),
|
||||
],
|
||||
matches: [
|
||||
|
|
|
|||
|
|
@ -150,4 +150,4 @@ SpanDetailsRepoReqAndRes get mockReponseWithChoices {
|
|||
// res.span.choices![1].selected = true;
|
||||
// res.span.message = "Conjugation error";
|
||||
// return res;
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import '../config/environment.dart';
|
||||
|
|
|
|||
|
|
@ -4,37 +4,13 @@ import 'dart:developer';
|
|||
import 'package:fluffychat/pangea/constants/model_keys.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import '../../widgets/matrix.dart';
|
||||
import '../models/user_model.dart';
|
||||
import '../models/user_profile_search_model.dart';
|
||||
import '../network/requests.dart';
|
||||
import '../network/urls.dart';
|
||||
|
||||
class PUserRepo {
|
||||
static Future<PUserModel> repoCreatePangeaUser({
|
||||
required String userID,
|
||||
required String dob,
|
||||
required fullName,
|
||||
required String matrixAccessToken,
|
||||
}) async {
|
||||
final Requests req = Requests(
|
||||
baseUrl: PApiUrls.baseAPI,
|
||||
matrixAccessToken: matrixAccessToken,
|
||||
);
|
||||
|
||||
final Map<String, dynamic> body = {
|
||||
ModelKey.userFullName: fullName,
|
||||
ModelKey.userPangeaUserId: userID,
|
||||
ModelKey.userDateOfBirth: dob,
|
||||
};
|
||||
final Response res = await req.post(
|
||||
url: PApiUrls.createUser,
|
||||
body: body,
|
||||
);
|
||||
return PUserModel.fromJson(jsonDecode(res.body));
|
||||
}
|
||||
|
||||
static Future<PUserModel?> fetchPangeaUserInfo({
|
||||
static Future<PangeaProfileResponse?> fetchPangeaUserInfo({
|
||||
required String userID,
|
||||
required String matrixAccessToken,
|
||||
}) async {
|
||||
|
|
@ -49,7 +25,7 @@ class PUserRepo {
|
|||
objectId: userID,
|
||||
);
|
||||
|
||||
return PUserModel.fromJson(jsonDecode(res.body));
|
||||
return PangeaProfileResponse.fromJson(jsonDecode(res.body));
|
||||
} catch (err) {
|
||||
//status code should be 400 - PTODO - check ffor this.
|
||||
log("Most likely a first signup and needs to make an account");
|
||||
|
|
@ -57,32 +33,6 @@ class PUserRepo {
|
|||
}
|
||||
}
|
||||
|
||||
//notes for jordan - only replace non-null fields, return whole profile
|
||||
//Jordan - should return pangeaUserId as well
|
||||
static Future<Profile> updateUserProfile(
|
||||
Profile userProfile,
|
||||
String accessToken,
|
||||
) async {
|
||||
final Requests req = Requests(
|
||||
baseUrl: PApiUrls.baseAPI,
|
||||
accessToken: accessToken,
|
||||
);
|
||||
final Response res = await req.put(
|
||||
url: PApiUrls.updateUserProfile,
|
||||
body: userProfile.toJson(),
|
||||
);
|
||||
|
||||
//temp fix
|
||||
final content = jsonDecode(res.body);
|
||||
//PTODO - try taking this out and see where bug occurs
|
||||
if (content[ModelKey.userPangeaUserId] == null) {
|
||||
content[ModelKey.userPangeaUserId] =
|
||||
MatrixState.pangeaController.matrixState.client.userID;
|
||||
}
|
||||
|
||||
return Profile.fromJson(content);
|
||||
}
|
||||
|
||||
static Future<UserProfileSearchResponse> searchUserProfiles({
|
||||
// List<String>? interests,
|
||||
String? targetLanguage,
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
bool canAddToSpace(Room space, PangeaController pangeaController) {
|
||||
final bool pangeaPermission =
|
||||
pangeaController.permissionsController.canUserGroupChat(roomID: space.id);
|
||||
final Map<String, dynamic> powerLevelsMap =
|
||||
space.getState(EventTypes.RoomPowerLevels)?.content ?? {};
|
||||
final pl = powerLevelsMap
|
||||
.tryGetMap<String, dynamic>('events')
|
||||
?.tryGet<int>(EventTypes.SpaceChild) ??
|
||||
powerLevelsMap.tryGet<int>('events_default') ??
|
||||
50;
|
||||
return space.ownPowerLevel >= pl && pangeaPermission;
|
||||
}
|
||||
|
||||
bool chatIsInSpace(Room chat, Room space) {
|
||||
return chat.spaceParents.map((e) => e.roomId).toList().contains(space.id);
|
||||
}
|
||||
|
||||
Future<void> pangeaAddToSpace(
|
||||
Room space,
|
||||
List<String> selectedRoomIds,
|
||||
BuildContext context,
|
||||
PangeaController pangeaController, {
|
||||
bool suggested = true,
|
||||
}) async {
|
||||
if (!canAddToSpace(space, pangeaController)) {
|
||||
throw L10n.of(context)!.noAddToSpacePermissions;
|
||||
}
|
||||
for (final roomId in selectedRoomIds) {
|
||||
final Room? room = Matrix.of(context).client.getRoomById(roomId);
|
||||
if (room != null && chatIsInSpace(room, space)) {
|
||||
throw L10n.of(context)!.alreadyInSpace;
|
||||
}
|
||||
await space.setSpaceChild(roomId, suggested: suggested);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import '../extensions/pangea_room_extension/pangea_room_extension.dart';
|
|||
class ClassChatPowerLevels {
|
||||
static Future<Map<String, dynamic>> powerLevelOverrideForClassChat(
|
||||
BuildContext context,
|
||||
List<Room> spaceParents,
|
||||
Room? parent,
|
||||
) async {
|
||||
final Client client = Matrix.of(context).client;
|
||||
final Map<String, dynamic> powerLevelOverride = {};
|
||||
|
|
@ -18,8 +18,9 @@ class ClassChatPowerLevels {
|
|||
powerLevelOverride['users'] = {};
|
||||
|
||||
final List<User> spaceAdmin = [];
|
||||
for (final classRoom in spaceParents) {
|
||||
final List<User> classTeachers = await classRoom.teachers;
|
||||
|
||||
if (parent != null) {
|
||||
final List<User> classTeachers = await parent.teachers;
|
||||
spaceAdmin.addAll(classTeachers);
|
||||
}
|
||||
|
||||
|
|
|
|||
515
lib/pangea/utils/country_display.dart
Normal file
515
lib/pangea/utils/country_display.dart
Normal file
|
|
@ -0,0 +1,515 @@
|
|||
import 'package:country_picker/country_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
class CountryDisplayUtil {
|
||||
/// used in find a partner page for display partner's country
|
||||
static String flagEmoji(String? countryName) {
|
||||
countryName = countryName?.split(' (')[0];
|
||||
final Country? country = CountryService().findByName(countryName);
|
||||
return country?.flagEmoji ?? "";
|
||||
}
|
||||
|
||||
static String? countryDisplayName(String? countryName, BuildContext context) {
|
||||
countryName = countryName?.split(' (')[0];
|
||||
final Country? country = CountryService().findByName(countryName);
|
||||
if (country?.countryCode == null) return null;
|
||||
switch (country!.countryCode) {
|
||||
case 'WW':
|
||||
return L10n.of(context)!.wwCountryDisplayName;
|
||||
case 'AF':
|
||||
return L10n.of(context)!.afCountryDisplayName;
|
||||
case 'AX':
|
||||
return L10n.of(context)!.axCountryDisplayName;
|
||||
case 'AL':
|
||||
return L10n.of(context)!.alCountryDisplayName;
|
||||
case 'DZ':
|
||||
return L10n.of(context)!.dzCountryDisplayName;
|
||||
case 'AS':
|
||||
return L10n.of(context)!.asCountryDisplayName;
|
||||
case 'AD':
|
||||
return L10n.of(context)!.adCountryDisplayName;
|
||||
case 'AO':
|
||||
return L10n.of(context)!.aoCountryDisplayName;
|
||||
case 'AI':
|
||||
return L10n.of(context)!.aiCountryDisplayName;
|
||||
case 'AG':
|
||||
return L10n.of(context)!.agCountryDisplayName;
|
||||
case 'AR':
|
||||
return L10n.of(context)!.arCountryDisplayName;
|
||||
case 'AM':
|
||||
return L10n.of(context)!.amCountryDisplayName;
|
||||
case 'AW':
|
||||
return L10n.of(context)!.awCountryDisplayName;
|
||||
case 'AC':
|
||||
return L10n.of(context)!.acCountryDisplayName;
|
||||
case 'AU':
|
||||
return L10n.of(context)!.auCountryDisplayName;
|
||||
case 'AT':
|
||||
return L10n.of(context)!.atCountryDisplayName;
|
||||
case 'AZ':
|
||||
return L10n.of(context)!.azCountryDisplayName;
|
||||
case 'BS':
|
||||
return L10n.of(context)!.bsCountryDisplayName;
|
||||
case 'BH':
|
||||
return L10n.of(context)!.bhCountryDisplayName;
|
||||
case 'BD':
|
||||
return L10n.of(context)!.bdCountryDisplayName;
|
||||
case 'BB':
|
||||
return L10n.of(context)!.bbCountryDisplayName;
|
||||
case 'BY':
|
||||
return L10n.of(context)!.byCountryDisplayName;
|
||||
case 'BE':
|
||||
return L10n.of(context)!.beCountryDisplayName;
|
||||
case 'BZ':
|
||||
return L10n.of(context)!.bzCountryDisplayName;
|
||||
case 'BJ':
|
||||
return L10n.of(context)!.bjCountryDisplayName;
|
||||
case 'BM':
|
||||
return L10n.of(context)!.bmCountryDisplayName;
|
||||
case 'BT':
|
||||
return L10n.of(context)!.btCountryDisplayName;
|
||||
case 'BO':
|
||||
return L10n.of(context)!.boCountryDisplayName;
|
||||
case 'BA':
|
||||
return L10n.of(context)!.baCountryDisplayName;
|
||||
case 'BW':
|
||||
return L10n.of(context)!.bwCountryDisplayName;
|
||||
case 'BR':
|
||||
return L10n.of(context)!.brCountryDisplayName;
|
||||
case 'IO':
|
||||
return L10n.of(context)!.ioCountryDisplayName;
|
||||
case 'VG':
|
||||
return L10n.of(context)!.vgCountryDisplayName;
|
||||
case 'BN':
|
||||
return L10n.of(context)!.bnCountryDisplayName;
|
||||
case 'BG':
|
||||
return L10n.of(context)!.bgCountryDisplayName;
|
||||
case 'BF':
|
||||
return L10n.of(context)!.bfCountryDisplayName;
|
||||
case 'BI':
|
||||
return L10n.of(context)!.biCountryDisplayName;
|
||||
case 'KH':
|
||||
return L10n.of(context)!.khCountryDisplayName;
|
||||
case 'CM':
|
||||
return L10n.of(context)!.cmCountryDisplayName;
|
||||
case 'CA':
|
||||
return L10n.of(context)!.caCountryDisplayName;
|
||||
case 'CV':
|
||||
return L10n.of(context)!.cvCountryDisplayName;
|
||||
case 'BQ':
|
||||
return L10n.of(context)!.bqCountryDisplayName;
|
||||
case 'KY':
|
||||
return L10n.of(context)!.kyCountryDisplayName;
|
||||
case 'CF':
|
||||
return L10n.of(context)!.cfCountryDisplayName;
|
||||
case 'TD':
|
||||
return L10n.of(context)!.tdCountryDisplayName;
|
||||
case 'CL':
|
||||
return L10n.of(context)!.clCountryDisplayName;
|
||||
case 'CN':
|
||||
return L10n.of(context)!.cnCountryDisplayName;
|
||||
case 'CX':
|
||||
return L10n.of(context)!.cxCountryDisplayName;
|
||||
case 'CC':
|
||||
return L10n.of(context)!.ccCountryDisplayName;
|
||||
case 'CO':
|
||||
return L10n.of(context)!.coCountryDisplayName;
|
||||
case 'KM':
|
||||
return L10n.of(context)!.kmCountryDisplayName;
|
||||
case 'CD':
|
||||
return L10n.of(context)!.cdCountryDisplayName;
|
||||
case 'CG':
|
||||
return L10n.of(context)!.cgCountryDisplayName;
|
||||
case 'CK':
|
||||
return L10n.of(context)!.ckCountryDisplayName;
|
||||
case 'CR':
|
||||
return L10n.of(context)!.crCountryDisplayName;
|
||||
case 'CI':
|
||||
return L10n.of(context)!.ciCountryDisplayName;
|
||||
case 'HR':
|
||||
return L10n.of(context)!.hrCountryDisplayName;
|
||||
case 'CU':
|
||||
return L10n.of(context)!.cuCountryDisplayName;
|
||||
case 'CW':
|
||||
return L10n.of(context)!.cwCountryDisplayName;
|
||||
case 'CY':
|
||||
return L10n.of(context)!.cyCountryDisplayName;
|
||||
case 'CZ':
|
||||
return L10n.of(context)!.czCountryDisplayName;
|
||||
case 'DK':
|
||||
return L10n.of(context)!.dkCountryDisplayName;
|
||||
case 'DJ':
|
||||
return L10n.of(context)!.djCountryDisplayName;
|
||||
case 'DM':
|
||||
return L10n.of(context)!.dmCountryDisplayName;
|
||||
case 'DO':
|
||||
return L10n.of(context)!.doCountryDisplayName;
|
||||
case 'TL':
|
||||
return L10n.of(context)!.tlCountryDisplayName;
|
||||
case 'EC':
|
||||
return L10n.of(context)!.ecCountryDisplayName;
|
||||
case 'EG':
|
||||
return L10n.of(context)!.egCountryDisplayName;
|
||||
case 'SV':
|
||||
return L10n.of(context)!.svCountryDisplayName;
|
||||
case 'GQ':
|
||||
return L10n.of(context)!.gqCountryDisplayName;
|
||||
case 'ER':
|
||||
return L10n.of(context)!.erCountryDisplayName;
|
||||
case 'EE':
|
||||
return L10n.of(context)!.eeCountryDisplayName;
|
||||
case 'SZ':
|
||||
return L10n.of(context)!.szCountryDisplayName;
|
||||
case 'ET':
|
||||
return L10n.of(context)!.etCountryDisplayName;
|
||||
case 'FK':
|
||||
return L10n.of(context)!.fkCountryDisplayName;
|
||||
case 'FO':
|
||||
return L10n.of(context)!.foCountryDisplayName;
|
||||
case 'FJ':
|
||||
return L10n.of(context)!.fjCountryDisplayName;
|
||||
case 'FI':
|
||||
return L10n.of(context)!.fiCountryDisplayName;
|
||||
case 'FR':
|
||||
return L10n.of(context)!.frCountryDisplayName;
|
||||
case 'GF':
|
||||
return L10n.of(context)!.gfCountryDisplayName;
|
||||
case 'PF':
|
||||
return L10n.of(context)!.pfCountryDisplayName;
|
||||
case 'GA':
|
||||
return L10n.of(context)!.gaCountryDisplayName;
|
||||
case 'GM':
|
||||
return L10n.of(context)!.gmCountryDisplayName;
|
||||
case 'GE':
|
||||
return L10n.of(context)!.geCountryDisplayName;
|
||||
case 'DE':
|
||||
return L10n.of(context)!.deCountryDisplayName;
|
||||
case 'GH':
|
||||
return L10n.of(context)!.ghCountryDisplayName;
|
||||
case 'GI':
|
||||
return L10n.of(context)!.giCountryDisplayName;
|
||||
case 'GR':
|
||||
return L10n.of(context)!.grCountryDisplayName;
|
||||
case 'GL':
|
||||
return L10n.of(context)!.glCountryDisplayName;
|
||||
case 'GD':
|
||||
return L10n.of(context)!.gdCountryDisplayName;
|
||||
case 'GP':
|
||||
return L10n.of(context)!.gpCountryDisplayName;
|
||||
case 'GU':
|
||||
return L10n.of(context)!.guCountryDisplayName;
|
||||
case 'GT':
|
||||
return L10n.of(context)!.gtCountryDisplayName;
|
||||
case 'GG':
|
||||
return L10n.of(context)!.ggCountryDisplayName;
|
||||
case 'GN':
|
||||
return L10n.of(context)!.gnCountryDisplayName;
|
||||
case 'GW':
|
||||
return L10n.of(context)!.gwCountryDisplayName;
|
||||
case 'GY':
|
||||
return L10n.of(context)!.gyCountryDisplayName;
|
||||
case 'HT':
|
||||
return L10n.of(context)!.htCountryDisplayName;
|
||||
case 'HM':
|
||||
return L10n.of(context)!.hmCountryDisplayName;
|
||||
case 'HN':
|
||||
return L10n.of(context)!.hnCountryDisplayName;
|
||||
case 'HK':
|
||||
return L10n.of(context)!.hkCountryDisplayName;
|
||||
case 'HU':
|
||||
return L10n.of(context)!.huCountryDisplayName;
|
||||
case 'IS':
|
||||
return L10n.of(context)!.isCountryDisplayName;
|
||||
case 'IN':
|
||||
return L10n.of(context)!.inCountryDisplayName;
|
||||
case 'ID':
|
||||
return L10n.of(context)!.idCountryDisplayName;
|
||||
case 'IR':
|
||||
return L10n.of(context)!.irCountryDisplayName;
|
||||
case 'IQ':
|
||||
return L10n.of(context)!.iqCountryDisplayName;
|
||||
case 'IE':
|
||||
return L10n.of(context)!.ieCountryDisplayName;
|
||||
case 'IM':
|
||||
return L10n.of(context)!.imCountryDisplayName;
|
||||
case 'IL':
|
||||
return L10n.of(context)!.ilCountryDisplayName;
|
||||
case 'IT':
|
||||
return L10n.of(context)!.itCountryDisplayName;
|
||||
case 'JM':
|
||||
return L10n.of(context)!.jmCountryDisplayName;
|
||||
case 'JP':
|
||||
return L10n.of(context)!.jpCountryDisplayName;
|
||||
case 'JE':
|
||||
return L10n.of(context)!.jeCountryDisplayName;
|
||||
case 'JO':
|
||||
return L10n.of(context)!.joCountryDisplayName;
|
||||
case 'KZ':
|
||||
return L10n.of(context)!.kzCountryDisplayName;
|
||||
case 'KE':
|
||||
return L10n.of(context)!.keCountryDisplayName;
|
||||
case 'KI':
|
||||
return L10n.of(context)!.kiCountryDisplayName;
|
||||
case 'XK':
|
||||
return L10n.of(context)!.xkCountryDisplayName;
|
||||
case 'KW':
|
||||
return L10n.of(context)!.kwCountryDisplayName;
|
||||
case 'KG':
|
||||
return L10n.of(context)!.kgCountryDisplayName;
|
||||
case 'LA':
|
||||
return L10n.of(context)!.laCountryDisplayName;
|
||||
case 'LV':
|
||||
return L10n.of(context)!.lvCountryDisplayName;
|
||||
case 'LB':
|
||||
return L10n.of(context)!.lbCountryDisplayName;
|
||||
case 'LS':
|
||||
return L10n.of(context)!.lsCountryDisplayName;
|
||||
case 'LR':
|
||||
return L10n.of(context)!.lrCountryDisplayName;
|
||||
case 'LY':
|
||||
return L10n.of(context)!.lyCountryDisplayName;
|
||||
case 'LI':
|
||||
return L10n.of(context)!.liCountryDisplayName;
|
||||
case 'LT':
|
||||
return L10n.of(context)!.ltCountryDisplayName;
|
||||
case 'LU':
|
||||
return L10n.of(context)!.luCountryDisplayName;
|
||||
case 'MO':
|
||||
return L10n.of(context)!.moCountryDisplayName;
|
||||
case 'MK':
|
||||
return L10n.of(context)!.mkCountryDisplayName;
|
||||
case 'MG':
|
||||
return L10n.of(context)!.mgCountryDisplayName;
|
||||
case 'MW':
|
||||
return L10n.of(context)!.mwCountryDisplayName;
|
||||
case 'MY':
|
||||
return L10n.of(context)!.myCountryDisplayName;
|
||||
case 'MV':
|
||||
return L10n.of(context)!.mvCountryDisplayName;
|
||||
case 'ML':
|
||||
return L10n.of(context)!.mlCountryDisplayName;
|
||||
case 'MT':
|
||||
return L10n.of(context)!.mtCountryDisplayName;
|
||||
case 'MH':
|
||||
return L10n.of(context)!.mhCountryDisplayName;
|
||||
case 'MQ':
|
||||
return L10n.of(context)!.mqCountryDisplayName;
|
||||
case 'MR':
|
||||
return L10n.of(context)!.mrCountryDisplayName;
|
||||
case 'MU':
|
||||
return L10n.of(context)!.muCountryDisplayName;
|
||||
case 'YT':
|
||||
return L10n.of(context)!.ytCountryDisplayName;
|
||||
case 'MX':
|
||||
return L10n.of(context)!.mxCountryDisplayName;
|
||||
case 'FM':
|
||||
return L10n.of(context)!.fmCountryDisplayName;
|
||||
case 'MD':
|
||||
return L10n.of(context)!.mdCountryDisplayName;
|
||||
case 'MC':
|
||||
return L10n.of(context)!.mcCountryDisplayName;
|
||||
case 'MN':
|
||||
return L10n.of(context)!.mnCountryDisplayName;
|
||||
case 'ME':
|
||||
return L10n.of(context)!.meCountryDisplayName;
|
||||
case 'MS':
|
||||
return L10n.of(context)!.msCountryDisplayName;
|
||||
case 'MA':
|
||||
return L10n.of(context)!.maCountryDisplayName;
|
||||
case 'MZ':
|
||||
return L10n.of(context)!.mzCountryDisplayName;
|
||||
case 'MM':
|
||||
return L10n.of(context)!.mmCountryDisplayName;
|
||||
case 'NA':
|
||||
return L10n.of(context)!.naCountryDisplayName;
|
||||
case 'NR':
|
||||
return L10n.of(context)!.nrCountryDisplayName;
|
||||
case 'NP':
|
||||
return L10n.of(context)!.npCountryDisplayName;
|
||||
case 'NL':
|
||||
return L10n.of(context)!.nlCountryDisplayName;
|
||||
case 'NC':
|
||||
return L10n.of(context)!.ncCountryDisplayName;
|
||||
case 'NZ':
|
||||
return L10n.of(context)!.nzCountryDisplayName;
|
||||
case 'NI':
|
||||
return L10n.of(context)!.niCountryDisplayName;
|
||||
case 'NE':
|
||||
return L10n.of(context)!.neCountryDisplayName;
|
||||
case 'NG':
|
||||
return L10n.of(context)!.ngCountryDisplayName;
|
||||
case 'NU':
|
||||
return L10n.of(context)!.nuCountryDisplayName;
|
||||
case 'NF':
|
||||
return L10n.of(context)!.nfCountryDisplayName;
|
||||
case 'KP':
|
||||
return L10n.of(context)!.kpCountryDisplayName;
|
||||
case 'MP':
|
||||
return L10n.of(context)!.mpCountryDisplayName;
|
||||
case 'NO':
|
||||
return L10n.of(context)!.noCountryDisplayName;
|
||||
case 'OM':
|
||||
return L10n.of(context)!.omCountryDisplayName;
|
||||
case 'PK':
|
||||
return L10n.of(context)!.pkCountryDisplayName;
|
||||
case 'PW':
|
||||
return L10n.of(context)!.pwCountryDisplayName;
|
||||
case 'PS':
|
||||
return L10n.of(context)!.psCountryDisplayName;
|
||||
case 'PA':
|
||||
return L10n.of(context)!.paCountryDisplayName;
|
||||
case 'PG':
|
||||
return L10n.of(context)!.pgCountryDisplayName;
|
||||
case 'PY':
|
||||
return L10n.of(context)!.pyCountryDisplayName;
|
||||
case 'PE':
|
||||
return L10n.of(context)!.peCountryDisplayName;
|
||||
case 'PH':
|
||||
return L10n.of(context)!.phCountryDisplayName;
|
||||
case 'PL':
|
||||
return L10n.of(context)!.plCountryDisplayName;
|
||||
case 'PT':
|
||||
return L10n.of(context)!.ptCountryDisplayName;
|
||||
case 'PR':
|
||||
return L10n.of(context)!.prCountryDisplayName;
|
||||
case 'QA':
|
||||
return L10n.of(context)!.qaCountryDisplayName;
|
||||
case 'RE':
|
||||
return L10n.of(context)!.reCountryDisplayName;
|
||||
case 'RO':
|
||||
return L10n.of(context)!.roCountryDisplayName;
|
||||
case 'RU':
|
||||
return L10n.of(context)!.ruCountryDisplayName;
|
||||
case 'RW':
|
||||
return L10n.of(context)!.rwCountryDisplayName;
|
||||
case 'BL':
|
||||
return L10n.of(context)!.blCountryDisplayName;
|
||||
case 'SH':
|
||||
return L10n.of(context)!.shCountryDisplayName;
|
||||
case 'KN':
|
||||
return L10n.of(context)!.knCountryDisplayName;
|
||||
case 'LC':
|
||||
return L10n.of(context)!.lcCountryDisplayName;
|
||||
case 'MF':
|
||||
return L10n.of(context)!.mfCountryDisplayName;
|
||||
case 'PM':
|
||||
return L10n.of(context)!.pmCountryDisplayName;
|
||||
case 'VC':
|
||||
return L10n.of(context)!.vcCountryDisplayName;
|
||||
case 'WS':
|
||||
return L10n.of(context)!.wsCountryDisplayName;
|
||||
case 'SM':
|
||||
return L10n.of(context)!.smCountryDisplayName;
|
||||
case 'ST':
|
||||
return L10n.of(context)!.stCountryDisplayName;
|
||||
case 'SA':
|
||||
return L10n.of(context)!.saCountryDisplayName;
|
||||
case 'SN':
|
||||
return L10n.of(context)!.snCountryDisplayName;
|
||||
case 'RS':
|
||||
return L10n.of(context)!.rsCountryDisplayName;
|
||||
case 'SC':
|
||||
return L10n.of(context)!.scCountryDisplayName;
|
||||
case 'SL':
|
||||
return L10n.of(context)!.slCountryDisplayName;
|
||||
case 'SG':
|
||||
return L10n.of(context)!.sgCountryDisplayName;
|
||||
case 'SX':
|
||||
return L10n.of(context)!.sxCountryDisplayName;
|
||||
case 'SK':
|
||||
return L10n.of(context)!.skCountryDisplayName;
|
||||
case 'SI':
|
||||
return L10n.of(context)!.siCountryDisplayName;
|
||||
case 'SB':
|
||||
return L10n.of(context)!.sbCountryDisplayName;
|
||||
case 'SO':
|
||||
return L10n.of(context)!.soCountryDisplayName;
|
||||
case 'ZA':
|
||||
return L10n.of(context)!.zaCountryDisplayName;
|
||||
case 'GS':
|
||||
return L10n.of(context)!.gsCountryDisplayName;
|
||||
case 'KR':
|
||||
return L10n.of(context)!.krCountryDisplayName;
|
||||
case 'SS':
|
||||
return L10n.of(context)!.ssCountryDisplayName;
|
||||
case 'ES':
|
||||
return L10n.of(context)!.esCountryDisplayName;
|
||||
case 'LK':
|
||||
return L10n.of(context)!.lkCountryDisplayName;
|
||||
case 'SD':
|
||||
return L10n.of(context)!.sdCountryDisplayName;
|
||||
case 'SR':
|
||||
return L10n.of(context)!.srCountryDisplayName;
|
||||
case 'SJ':
|
||||
return L10n.of(context)!.sjCountryDisplayName;
|
||||
case 'SE':
|
||||
return L10n.of(context)!.seCountryDisplayName;
|
||||
case 'CH':
|
||||
return L10n.of(context)!.chCountryDisplayName;
|
||||
case 'SY':
|
||||
return L10n.of(context)!.syCountryDisplayName;
|
||||
case 'TW':
|
||||
return L10n.of(context)!.twCountryDisplayName;
|
||||
case 'TJ':
|
||||
return L10n.of(context)!.tjCountryDisplayName;
|
||||
case 'TZ':
|
||||
return L10n.of(context)!.tzCountryDisplayName;
|
||||
case 'TH':
|
||||
return L10n.of(context)!.thCountryDisplayName;
|
||||
case 'TG':
|
||||
return L10n.of(context)!.tgCountryDisplayName;
|
||||
case 'TK':
|
||||
return L10n.of(context)!.tkCountryDisplayName;
|
||||
case 'TO':
|
||||
return L10n.of(context)!.toCountryDisplayName;
|
||||
case 'TT':
|
||||
return L10n.of(context)!.ttCountryDisplayName;
|
||||
case 'TN':
|
||||
return L10n.of(context)!.tnCountryDisplayName;
|
||||
case 'TR':
|
||||
return L10n.of(context)!.trCountryDisplayName;
|
||||
case 'TM':
|
||||
return L10n.of(context)!.tmCountryDisplayName;
|
||||
case 'TC':
|
||||
return L10n.of(context)!.tcCountryDisplayName;
|
||||
case 'TV':
|
||||
return L10n.of(context)!.tvCountryDisplayName;
|
||||
case 'VI':
|
||||
return L10n.of(context)!.viCountryDisplayName;
|
||||
case 'UG':
|
||||
return L10n.of(context)!.ugCountryDisplayName;
|
||||
case 'UA':
|
||||
return L10n.of(context)!.uaCountryDisplayName;
|
||||
case 'AE':
|
||||
return L10n.of(context)!.aeCountryDisplayName;
|
||||
case 'GB':
|
||||
return L10n.of(context)!.gbCountryDisplayName;
|
||||
case 'US':
|
||||
return L10n.of(context)!.usCountryDisplayName;
|
||||
case 'UY':
|
||||
return L10n.of(context)!.uyCountryDisplayName;
|
||||
case 'UZ':
|
||||
return L10n.of(context)!.uzCountryDisplayName;
|
||||
case 'VU':
|
||||
return L10n.of(context)!.vuCountryDisplayName;
|
||||
case 'VA':
|
||||
return L10n.of(context)!.vaCountryDisplayName;
|
||||
case 'VE':
|
||||
return L10n.of(context)!.veCountryDisplayName;
|
||||
case 'VN':
|
||||
return L10n.of(context)!.vnCountryDisplayName;
|
||||
case 'WF':
|
||||
return L10n.of(context)!.wfCountryDisplayName;
|
||||
case 'EH':
|
||||
return L10n.of(context)!.ehCountryDisplayName;
|
||||
case 'YE':
|
||||
return L10n.of(context)!.yeCountryDisplayName;
|
||||
case 'ZM':
|
||||
return L10n.of(context)!.zmCountryDisplayName;
|
||||
case 'ZW':
|
||||
return L10n.of(context)!.zwCountryDisplayName;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,9 @@ import 'package:sentry_flutter/sentry_flutter.dart';
|
|||
class PangeaWarningError implements Exception {
|
||||
final String message;
|
||||
PangeaWarningError(message) : message = "Pangea Warning Error: $message";
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
class ErrorHandler {
|
||||
|
|
@ -53,8 +56,13 @@ class ErrorHandler {
|
|||
Map<String, dynamic>? data,
|
||||
SentryLevel level = SentryLevel.error,
|
||||
}) async {
|
||||
if (m != null) debugPrint("error message: $m");
|
||||
if ((e ?? m) != null) debugPrint("error to string: ${e?.toString() ?? m}");
|
||||
if (e is PangeaWarningError) {
|
||||
// Custom handling for PangeaWarningError
|
||||
debugPrint("PangeaWarningError: ${e.message}");
|
||||
} else {
|
||||
if (m != null) debugPrint("error message: $m");
|
||||
}
|
||||
|
||||
if (data != null) {
|
||||
Sentry.addBreadcrumb(Breadcrumb.fromJson(data));
|
||||
debugPrint(data.toString());
|
||||
|
|
|
|||
|
|
@ -82,15 +82,19 @@ class GetChatListItemSubtitle {
|
|||
|
||||
final i18n = MatrixLocals(l10n);
|
||||
|
||||
if (text == null) return l10n.emptyChat;
|
||||
if (text == null || event.room.lastEvent == null) {
|
||||
return l10n.emptyChat;
|
||||
}
|
||||
|
||||
if (!event.room.isDirectChat ||
|
||||
event.room.directChatMatrixID != event.room.lastEvent?.senderId) {
|
||||
event.room.directChatMatrixID != event.room.lastEvent!.senderId) {
|
||||
final senderNameOrYou = event.senderId == event.room.client.userID
|
||||
? i18n.you
|
||||
: event.room
|
||||
.unsafeGetUserFromMemoryOrFallback(event.senderId)
|
||||
.calcDisplayname(i18n: i18n);
|
||||
.getParticipants()
|
||||
.firstWhereOrNull((u) => u.id != event!.room.client.userID)
|
||||
?.calcDisplayname(i18n: i18n) ??
|
||||
event.room.lastEvent!.senderId;
|
||||
|
||||
return "$senderNameOrYou: $text";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class InlineTooltip extends StatelessWidget {
|
|||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: RichText(
|
||||
textAlign: TextAlign.justify,
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
children: [
|
||||
const WidgetSpan(
|
||||
|
|
@ -50,6 +50,7 @@ class InlineTooltip extends StatelessWidget {
|
|||
text: body,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
|
||||
import 'package:fluffychat/pangea/utils/inline_tooltip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -18,14 +19,15 @@ class InstructionsController {
|
|||
// We have these three methods to make sure that the instructions are not shown too much
|
||||
|
||||
/// Instruction popup was closed by the user
|
||||
final Map<InstructionsEnum, bool> _instructionsClosed = {};
|
||||
final Map<String, bool> _instructionsClosed = {};
|
||||
|
||||
/// Instruction popup has already been shown this session
|
||||
final Map<InstructionsEnum, bool> _instructionsShown = {};
|
||||
final Map<String, bool> _instructionsShown = {};
|
||||
|
||||
/// Returns true if the user requested this popup not be shown again
|
||||
bool? toggledOff(InstructionsEnum key) =>
|
||||
_pangeaController.pStoreService.read(key.toString());
|
||||
bool? toggledOff(String key) => InstructionsEnum.values
|
||||
.firstWhereOrNull((value) => value.toString() == key)
|
||||
?.toggledOff;
|
||||
|
||||
InstructionsController(PangeaController pangeaController) {
|
||||
_pangeaController = pangeaController;
|
||||
|
|
@ -33,20 +35,32 @@ class InstructionsController {
|
|||
|
||||
/// Returns true if the instructions were closed
|
||||
/// or turned off by the user via the toggle switch
|
||||
bool wereInstructionsTurnedOff(InstructionsEnum key) =>
|
||||
toggledOff(key) ?? _instructionsClosed[key] ?? false;
|
||||
bool wereInstructionsTurnedOff(String key) {
|
||||
return toggledOff(key) ?? _instructionsClosed[key] ?? false;
|
||||
}
|
||||
|
||||
void turnOffInstruction(InstructionsEnum key) =>
|
||||
_instructionsClosed[key] = true;
|
||||
void turnOffInstruction(String key) => _instructionsClosed[key] = true;
|
||||
|
||||
Future<void> updateEnableInstructions(
|
||||
InstructionsEnum key,
|
||||
void updateEnableInstructions(
|
||||
String key,
|
||||
bool value,
|
||||
) async =>
|
||||
await _pangeaController.pStoreService.save(
|
||||
key.toString(),
|
||||
value,
|
||||
);
|
||||
) {
|
||||
_pangeaController.userController.updateProfile((profile) {
|
||||
if (key == InstructionsEnum.itInstructions.toString()) {
|
||||
profile.instructionSettings.showedItInstructions = value;
|
||||
}
|
||||
if (key == InstructionsEnum.clickMessage.toString()) {
|
||||
profile.instructionSettings.showedClickMessage = value;
|
||||
}
|
||||
if (key == InstructionsEnum.blurMeansTranslate.toString()) {
|
||||
profile.instructionSettings.showedBlurMeansTranslate = value;
|
||||
}
|
||||
if (key == InstructionsEnum.tooltipInstructions.toString()) {
|
||||
profile.instructionSettings.showedTooltipInstructions = value;
|
||||
}
|
||||
return profile;
|
||||
});
|
||||
}
|
||||
|
||||
/// Instruction Card gives users tips on
|
||||
/// how to use Pangea Chat's features
|
||||
|
|
@ -56,12 +70,12 @@ class InstructionsController {
|
|||
String transformTargetKey, [
|
||||
bool showToggle = true,
|
||||
]) async {
|
||||
if (_instructionsShown[key] ?? false) {
|
||||
if (_instructionsShown[key.toString()] ?? false) {
|
||||
return;
|
||||
}
|
||||
_instructionsShown[key] = true;
|
||||
_instructionsShown[key.toString()] = true;
|
||||
|
||||
if (wereInstructionsTurnedOff(key)) {
|
||||
if (wereInstructionsTurnedOff(key.toString())) {
|
||||
return;
|
||||
}
|
||||
if (L10n.of(context) == null) {
|
||||
|
|
@ -90,7 +104,7 @@ class InstructionsController {
|
|||
CardHeader(
|
||||
text: key.title(context),
|
||||
botExpression: BotExpression.idle,
|
||||
onClose: () => {_instructionsClosed[key] = true},
|
||||
onClose: () => {_instructionsClosed[key.toString()] = true},
|
||||
),
|
||||
const SizedBox(height: 10.0),
|
||||
Expanded(
|
||||
|
|
@ -118,10 +132,10 @@ class InstructionsController {
|
|||
/// which displays hint text defined in the enum extension
|
||||
Widget getInstructionInlineTooltip(
|
||||
BuildContext context,
|
||||
InstructionsEnum key,
|
||||
InlineInstructions key,
|
||||
VoidCallback onClose,
|
||||
) {
|
||||
if (wereInstructionsTurnedOff(key)) {
|
||||
if (wereInstructionsTurnedOff(key.toString())) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +148,7 @@ class InstructionsController {
|
|||
}
|
||||
|
||||
return InlineTooltip(
|
||||
body: InstructionsEnum.speechToText.body(context),
|
||||
body: InlineInstructions.speechToText.body(context),
|
||||
onClose: onClose,
|
||||
);
|
||||
}
|
||||
|
|
@ -167,11 +181,12 @@ class InstructionsToggleState extends State<InstructionsToggle> {
|
|||
return SwitchListTile.adaptive(
|
||||
activeColor: AppConfig.activeToggleColor,
|
||||
title: Text(L10n.of(context)!.doNotShowAgain),
|
||||
value: pangeaController.instructions
|
||||
.wereInstructionsTurnedOff(widget.instructionsKey),
|
||||
value: pangeaController.instructions.wereInstructionsTurnedOff(
|
||||
widget.instructionsKey.toString(),
|
||||
),
|
||||
onChanged: ((value) async {
|
||||
await pangeaController.instructions.updateEnableInstructions(
|
||||
widget.instructionsKey,
|
||||
pangeaController.instructions.updateEnableInstructions(
|
||||
widget.instructionsKey.toString(),
|
||||
value,
|
||||
);
|
||||
setState(() {});
|
||||
|
|
|
|||
|
|
@ -1,133 +1,71 @@
|
|||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class PLocalStore {
|
||||
/// 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
|
||||
/// instance of this class is created in the PangeaController.
|
||||
class PStore {
|
||||
final GetStorage _box = GetStorage();
|
||||
final PangeaController pangeaController;
|
||||
|
||||
PLocalStore({required this.pangeaController});
|
||||
PStore({required this.pangeaController});
|
||||
|
||||
/// save data in local
|
||||
/// Saves the provided [data] with the specified [key] in the local storage.
|
||||
///
|
||||
/// By default, the [data] is considered as account data, but you can set
|
||||
/// [isAccountData] to false if it's not account-related data.
|
||||
///
|
||||
/// Example usage:
|
||||
/// ```dart
|
||||
/// await save('user', {'name': 'John Doe', 'age': 25});
|
||||
/// ```
|
||||
Future<void> save(
|
||||
String key,
|
||||
dynamic data, {
|
||||
bool addClientIdToKey = true,
|
||||
bool local = false,
|
||||
bool isAccountData = true,
|
||||
}) async {
|
||||
local
|
||||
? await saveLocal(
|
||||
key,
|
||||
data,
|
||||
addClientIdToKey: addClientIdToKey,
|
||||
)
|
||||
: await saveProfile(key, data);
|
||||
await _box.write(_key(key, isAccountData: isAccountData), data);
|
||||
}
|
||||
|
||||
/// fetch data from local
|
||||
dynamic read(
|
||||
String key, {
|
||||
bool addClientIdToKey = true,
|
||||
local = false,
|
||||
}) {
|
||||
return local
|
||||
? readLocal(
|
||||
key,
|
||||
addClientIdToKey: addClientIdToKey,
|
||||
)
|
||||
: readProfile(key);
|
||||
}
|
||||
|
||||
/// delete data from local
|
||||
Future<void> delete(
|
||||
String key, {
|
||||
bool addClientIdToKey = true,
|
||||
local = false,
|
||||
}) async {
|
||||
return local
|
||||
? deleteLocal(
|
||||
key,
|
||||
addClientIdToKey: addClientIdToKey,
|
||||
)
|
||||
: deleteProfile(key);
|
||||
}
|
||||
|
||||
/// save data in local
|
||||
Future<void> saveLocal(
|
||||
String key,
|
||||
dynamic data, {
|
||||
bool addClientIdToKey = true,
|
||||
}) async {
|
||||
await _box.write(_key(key, addClientIdToKey: addClientIdToKey), data);
|
||||
}
|
||||
|
||||
Future<void> saveProfile(
|
||||
String key,
|
||||
dynamic data,
|
||||
) async {
|
||||
final waitForAccountSync =
|
||||
pangeaController.matrixState.client.onSync.stream.firstWhere(
|
||||
(sync) =>
|
||||
sync.accountData != null &&
|
||||
sync.accountData!.any(
|
||||
(event) => event.content.keys.any(
|
||||
(k) => k == key,
|
||||
),
|
||||
),
|
||||
);
|
||||
await pangeaController.matrixState.client.setAccountData(
|
||||
pangeaController.matrixState.client.userID!,
|
||||
key,
|
||||
{key: data},
|
||||
);
|
||||
await waitForAccountSync;
|
||||
await pangeaController.matrixState.client.onSyncStatus.stream.firstWhere(
|
||||
(syncStatus) => syncStatus.status == SyncStatus.finished,
|
||||
);
|
||||
}
|
||||
|
||||
/// fetch data from local
|
||||
dynamic readLocal(String key, {bool addClientIdToKey = true}) {
|
||||
/// Reads the value associated with the given [key] from the local store.
|
||||
///
|
||||
/// If [isAccountData] is true, tries to find key assosiated with the logged in user.
|
||||
/// Otherwise, it is read from the general store.
|
||||
///
|
||||
/// Returns the value associated with the [key], or
|
||||
/// null if the user ID is null or value hasn't been set.
|
||||
dynamic read(String key, {bool isAccountData = true}) {
|
||||
return pangeaController.matrixState.client.userID != null
|
||||
? _box.read(_key(key, addClientIdToKey: addClientIdToKey))
|
||||
? _box.read(_key(key, isAccountData: isAccountData))
|
||||
: null;
|
||||
}
|
||||
|
||||
dynamic readProfile(String key) {
|
||||
try {
|
||||
return pangeaController.matrixState.client.accountData[key]?.content[key];
|
||||
} catch (err) {
|
||||
ErrorHandler.logError(e: err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// delete data from local
|
||||
Future<void> deleteLocal(String key, {bool addClientIdToKey = true}) async {
|
||||
/// Deletes the value associated with the given [key] from the local store.
|
||||
///
|
||||
/// If [isAccountData] is true (default), will try to use key assosiated with the logged in user's ID
|
||||
///
|
||||
/// Returns a [Future] that completes when the value is successfully deleted.
|
||||
/// If the user is not logged in, the value will not be deleted and the [Future] will complete with null.
|
||||
Future<void> delete(String key, {bool isAccountData = true}) async {
|
||||
return pangeaController.matrixState.client.userID != null
|
||||
? _box.remove(_key(key, addClientIdToKey: addClientIdToKey))
|
||||
? _box.remove(_key(key, isAccountData: isAccountData))
|
||||
: null;
|
||||
}
|
||||
|
||||
Future<void> deleteProfile(key) async {
|
||||
return pangeaController.matrixState.client.userID != null
|
||||
? pangeaController.matrixState.client.setAccountData(
|
||||
pangeaController.matrixState.client.userID!,
|
||||
key,
|
||||
{key: null},
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
_key(String key, {bool addClientIdToKey = true}) {
|
||||
return addClientIdToKey
|
||||
/// Returns the key for storing data in the pangea store.
|
||||
///
|
||||
/// The [key] parameter represents the base key for the data.
|
||||
/// The [isAccountData] parameter indicates whether the data is account-specific.
|
||||
/// If [isAccountData] is true, the account-specific key is returned by appending the user ID to the base key.
|
||||
/// If [isAccountData] is false, the base key is returned as is.
|
||||
String _key(String key, {bool isAccountData = true}) {
|
||||
return isAccountData
|
||||
? pangeaController.matrixState.client.userID! + key
|
||||
: key;
|
||||
}
|
||||
|
||||
/// clear all local storage
|
||||
clearStorage() {
|
||||
/// Clears the storage by erasing all data in the box.
|
||||
void clearStorage() {
|
||||
_box.erase();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
94
lib/pangea/widgets/chat/chat_floating_action_button.dart
Normal file
94
lib/pangea/widgets/chat/chat_floating_action_button.dart
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/has_error_button.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/language_permissions_warning_buttons.dart';
|
||||
import 'package:fluffychat/pangea/models/space_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChatFloatingActionButton extends StatefulWidget {
|
||||
final ChatController controller;
|
||||
const ChatFloatingActionButton({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
ChatFloatingActionButtonState createState() =>
|
||||
ChatFloatingActionButtonState();
|
||||
}
|
||||
|
||||
class ChatFloatingActionButtonState extends State<ChatFloatingActionButton> {
|
||||
bool showPermissionsError = false;
|
||||
StreamSubscription? _choreoSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final permissionsController =
|
||||
widget.controller.pangeaController.permissionsController;
|
||||
final itEnabled = permissionsController.isToolEnabled(
|
||||
ToolSetting.interactiveTranslator,
|
||||
widget.controller.room,
|
||||
);
|
||||
final igcEnabled = permissionsController.isToolEnabled(
|
||||
ToolSetting.interactiveGrammar,
|
||||
widget.controller.room,
|
||||
);
|
||||
showPermissionsError = !itEnabled || !igcEnabled;
|
||||
debugPrint("showPermissionsError: $showPermissionsError");
|
||||
|
||||
if (showPermissionsError) {
|
||||
Future.delayed(
|
||||
const Duration(seconds: 5),
|
||||
() {
|
||||
if (mounted) setState(() => showPermissionsError = false);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Rebuild the widget each time there's an update from choreo (i.e., an error).
|
||||
_choreoSub =
|
||||
widget.controller.choreographer.stateListener.stream.listen((_) {
|
||||
setState(() {});
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_choreoSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.controller.selectedEvents.isNotEmpty) {
|
||||
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),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (widget.controller.choreographer.errorService.error != null) {
|
||||
return ChoreographerHasErrorButton(
|
||||
widget.controller.pangeaController,
|
||||
widget.controller.choreographer.errorService.error!,
|
||||
);
|
||||
}
|
||||
|
||||
return showPermissionsError
|
||||
? LanguagePermissionsButtons(
|
||||
choreographer: widget.controller.choreographer,
|
||||
roomID: widget.controller.roomId,
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
82
lib/pangea/widgets/chat/input_bar_wrapper.dart
Normal file
82
lib/pangea/widgets/chat/input_bar_wrapper.dart
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fluffychat/pages/chat/input_bar.dart';
|
||||
import 'package:fluffychat/pangea/widgets/igc/pangea_text_controller.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class InputBarWrapper extends StatefulWidget {
|
||||
final Room room;
|
||||
final int? minLines;
|
||||
final int? maxLines;
|
||||
final TextInputType? keyboardType;
|
||||
final TextInputAction? textInputAction;
|
||||
final ValueChanged<String>? onSubmitted;
|
||||
final ValueChanged<Uint8List?>? onSubmitImage;
|
||||
final FocusNode? focusNode;
|
||||
final PangeaTextController? controller;
|
||||
final InputDecoration? decoration;
|
||||
final ValueChanged<String>? onChanged;
|
||||
final bool? autofocus;
|
||||
final bool readOnly;
|
||||
|
||||
const InputBarWrapper({
|
||||
required this.room,
|
||||
this.minLines,
|
||||
this.maxLines,
|
||||
this.keyboardType,
|
||||
this.onSubmitted,
|
||||
this.onSubmitImage,
|
||||
this.focusNode,
|
||||
this.controller,
|
||||
this.decoration,
|
||||
this.onChanged,
|
||||
this.autofocus,
|
||||
this.textInputAction,
|
||||
this.readOnly = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<InputBarWrapper> createState() => InputBarWrapperState();
|
||||
}
|
||||
|
||||
class InputBarWrapperState extends State<InputBarWrapper> {
|
||||
StreamSubscription? _choreoSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
// Rebuild the widget each time there's an update from choreo
|
||||
_choreoSub =
|
||||
widget.controller?.choreographer.stateListener.stream.listen((_) {
|
||||
setState(() {});
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_choreoSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InputBar(
|
||||
room: widget.room,
|
||||
minLines: widget.minLines,
|
||||
maxLines: widget.maxLines,
|
||||
keyboardType: widget.keyboardType,
|
||||
onSubmitted: widget.onSubmitted,
|
||||
onSubmitImage: widget.onSubmitImage,
|
||||
focusNode: widget.focusNode,
|
||||
controller: widget.controller,
|
||||
decoration: widget.decoration,
|
||||
onChanged: widget.onChanged,
|
||||
autofocus: widget.autofocus,
|
||||
textInputAction: widget.textInputAction,
|
||||
readOnly: widget.readOnly,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/text_to_speech_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PangeaMessageActions extends StatelessWidget {
|
||||
final ChatController chatController;
|
||||
|
||||
const PangeaMessageActions({super.key, required this.chatController});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return chatController.selectedEvents.length == 1
|
||||
? Row(
|
||||
children: <Widget>[
|
||||
// LanguageToggleSwitch(controller: chatController),
|
||||
TextToSpeechButton(
|
||||
controller: chatController,
|
||||
selectedEvent: chatController.selectedEvents.first,
|
||||
),
|
||||
// IconButton(
|
||||
// icon: Icon(Icons.mic),
|
||||
// onPressed: chatController.onMicTap,
|
||||
// ),
|
||||
// Add more IconButton widgets here
|
||||
],
|
||||
)
|
||||
: const SizedBox();
|
||||
}
|
||||
}
|
||||
|
|
@ -67,7 +67,11 @@ class MessageSpeechToTextCardState extends State<MessageSpeechToTextCard> {
|
|||
|
||||
void closeHint() {
|
||||
MatrixState.pangeaController.instructions.turnOffInstruction(
|
||||
InstructionsEnum.speechToText,
|
||||
InlineInstructions.speechToText.toString(),
|
||||
);
|
||||
MatrixState.pangeaController.instructions.updateEnableInstructions(
|
||||
InlineInstructions.speechToText.toString(),
|
||||
true,
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
|
|
@ -204,7 +208,7 @@ class MessageSpeechToTextCardState extends State<MessageSpeechToTextCard> {
|
|||
),
|
||||
MatrixState.pangeaController.instructions.getInstructionInlineTooltip(
|
||||
context,
|
||||
InstructionsEnum.speechToText,
|
||||
InlineInstructions.speechToText,
|
||||
closeHint,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import 'dart:developer';
|
|||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/constants/local.key.dart';
|
||||
import 'package:fluffychat/pangea/enum/message_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/utils/any_state_holder.dart';
|
||||
|
|
@ -194,6 +193,8 @@ class MessageToolbarState extends State<MessageToolbar> {
|
|||
late StreamSubscription<MessageMode> toolbarModeStream;
|
||||
|
||||
void updateMode(MessageMode newMode) {
|
||||
//Early exit from the function if the widget has been unmounted to prevent updates on an inactive widget.
|
||||
if (!mounted) return;
|
||||
if (updatingMode) return;
|
||||
debugPrint("updating toolbar mode");
|
||||
final bool subscribed =
|
||||
|
|
@ -329,17 +330,13 @@ class MessageToolbarState extends State<MessageToolbar> {
|
|||
});
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
final bool autoplay = MatrixState.pangeaController.pStoreService.read(
|
||||
PLocalKey.autoPlayMessages,
|
||||
) ??
|
||||
false;
|
||||
|
||||
if (widget.pangeaMessageEvent.isAudioMessage) {
|
||||
updateMode(MessageMode.speechToText);
|
||||
return;
|
||||
}
|
||||
|
||||
autoplay
|
||||
MatrixState.pangeaController.userController.profile.userSettings
|
||||
.autoPlayMessages
|
||||
? updateMode(MessageMode.textToSpeech)
|
||||
: updateMode(MessageMode.translation);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/models/representation_content_model.dart';
|
||||
import 'package:fluffychat/pangea/repo/full_text_translation_repo.dart';
|
||||
import 'package:fluffychat/pangea/utils/bot_style.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/utils/inline_tooltip.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/message_text_selection.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator.dart';
|
||||
import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart';
|
||||
|
|
@ -119,6 +121,17 @@ class MessageTranslationCardState extends State<MessageTranslationCard> {
|
|||
}
|
||||
}
|
||||
|
||||
void closeHint() {
|
||||
MatrixState.pangeaController.instructions.turnOffInstruction(
|
||||
InlineInstructions.l1Translation.toString(),
|
||||
);
|
||||
MatrixState.pangeaController.instructions.updateEnableInstructions(
|
||||
InlineInstructions.l1Translation.toString(),
|
||||
true,
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_fetchingRepresentation &&
|
||||
|
|
@ -127,18 +140,35 @@ class MessageTranslationCardState extends State<MessageTranslationCard> {
|
|||
return const CardErrorWidget();
|
||||
}
|
||||
|
||||
final bool showWarning = l2Code != null &&
|
||||
!widget.immersionMode &&
|
||||
widget.messageEvent.originalSent?.langCode != l2Code &&
|
||||
!MatrixState.pangeaController.instructions.wereInstructionsTurnedOff(
|
||||
InlineInstructions.l1Translation.toString(),
|
||||
);
|
||||
|
||||
return Container(
|
||||
child: _fetchingRepresentation
|
||||
? const ToolbarContentLoadingIndicator()
|
||||
: selectionTranslation != null
|
||||
? Text(
|
||||
selectionTranslation!,
|
||||
style: BotStyle.text(context),
|
||||
)
|
||||
: Text(
|
||||
repEvent!.text,
|
||||
style: BotStyle.text(context),
|
||||
),
|
||||
: Column(
|
||||
children: [
|
||||
selectionTranslation != null
|
||||
? Text(
|
||||
selectionTranslation!,
|
||||
style: BotStyle.text(context),
|
||||
)
|
||||
: Text(
|
||||
repEvent!.text,
|
||||
style: BotStyle.text(context),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (showWarning)
|
||||
InlineTooltip(
|
||||
body: InlineInstructions.l1Translation.body(context),
|
||||
onClose: closeHint,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,138 +0,0 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/events/audio_player.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class TextToSpeechButton extends StatefulWidget {
|
||||
final ChatController controller;
|
||||
final Event selectedEvent;
|
||||
|
||||
const TextToSpeechButton({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.selectedEvent,
|
||||
});
|
||||
|
||||
@override
|
||||
_TextToSpeechButtonState createState() => _TextToSpeechButtonState();
|
||||
}
|
||||
|
||||
class _TextToSpeechButtonState extends State<TextToSpeechButton> {
|
||||
final AudioPlayer _audioPlayer = AudioPlayer();
|
||||
late PangeaMessageEvent _pangeaMessageEvent;
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_audioPlayer.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_pangeaMessageEvent = PangeaMessageEvent(
|
||||
event: widget.selectedEvent,
|
||||
timeline: widget.controller.timeline!,
|
||||
ownMessage:
|
||||
widget.selectedEvent.senderId == Matrix.of(context).client.userID,
|
||||
);
|
||||
}
|
||||
|
||||
Event? get localAudioEvent =>
|
||||
langCode != null && text != null && text!.isNotEmpty
|
||||
? _pangeaMessageEvent.getTextToSpeechLocal(langCode!, text!)
|
||||
: null;
|
||||
|
||||
String? get langCode =>
|
||||
widget.controller.choreographer.messageOptions.selectedDisplayLang
|
||||
?.langCode ??
|
||||
widget.controller.choreographer.l2LangCode;
|
||||
|
||||
String? get text => langCode != null
|
||||
? _pangeaMessageEvent.representationByLanguage(langCode!)?.text
|
||||
: null;
|
||||
|
||||
Future<void> _getAudio() async {
|
||||
try {
|
||||
if (!mounted) return;
|
||||
if (text == null || text!.isEmpty) return;
|
||||
if (langCode == null || langCode!.isEmpty) return;
|
||||
|
||||
setState(() => _isLoading = true);
|
||||
await _pangeaMessageEvent.getTextToSpeechGlobal(langCode!);
|
||||
setState(() => _isLoading = false);
|
||||
} catch (e) {
|
||||
setState(() => _isLoading = false);
|
||||
debugger(when: kDebugMode);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(L10n.of(context)!.errorGettingAudio),
|
||||
),
|
||||
);
|
||||
ErrorHandler.logError(
|
||||
e: Exception(),
|
||||
s: StackTrace.current,
|
||||
m: 'text is null or empty in text_to_speech_button.dart',
|
||||
data: {'selectedEvent': widget.selectedEvent, 'langCode': langCode},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final playButton = InkWell(
|
||||
borderRadius: BorderRadius.circular(64),
|
||||
onTap: text == null || text!.isEmpty ? null : _getAudio,
|
||||
child: Material(
|
||||
color: AppConfig.primaryColor.withAlpha(64),
|
||||
borderRadius: BorderRadius.circular(64),
|
||||
child: const Icon(
|
||||
// Change the icon based on some condition. If you have an audio player state, use it here.
|
||||
Icons.play_arrow_outlined,
|
||||
color: AppConfig.primaryColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return localAudioEvent == null
|
||||
? Opacity(
|
||||
opacity: text == null || text!.isEmpty ? 0.5 : 1,
|
||||
child: SizedBox(
|
||||
width: 44, // Match the size of the button in AudioPlayerState
|
||||
height: 36,
|
||||
child: Padding(
|
||||
//only left side of the button is padded to match the padding of the AudioPlayerState
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: playButton,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 250,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
AudioPlayerWidget(
|
||||
localAudioEvent!,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ class AddToSpaceToggles extends StatefulWidget {
|
|||
|
||||
class AddToSpaceState extends State<AddToSpaceToggles> {
|
||||
late Room? room;
|
||||
late List<Room> parents;
|
||||
late Room? parent;
|
||||
late List<Room> possibleParents;
|
||||
late bool isOpen;
|
||||
late bool isSuggested;
|
||||
|
|
@ -70,20 +70,17 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
|
|||
)
|
||||
.toList();
|
||||
|
||||
parents = widget.roomId != null
|
||||
? possibleParents
|
||||
.where(
|
||||
(r) =>
|
||||
r.spaceChildren.any((room) => room.roomId == widget.roomId),
|
||||
)
|
||||
.toList()
|
||||
: [];
|
||||
parent = widget.roomId != null
|
||||
? possibleParents.firstWhereOrNull(
|
||||
(r) => r.spaceChildren.any((room) => room.roomId == widget.roomId),
|
||||
)
|
||||
: null;
|
||||
|
||||
if (widget.activeSpaceId != null) {
|
||||
final activeSpace =
|
||||
Matrix.of(context).client.getRoomById(widget.activeSpaceId!);
|
||||
if (activeSpace != null && activeSpace.canIAddSpaceChild(null)) {
|
||||
parents.add(activeSpace);
|
||||
parent = activeSpace;
|
||||
} else {
|
||||
ErrorHandler.logError(
|
||||
e: Exception('activeSpaceId ${widget.activeSpaceId} not found'),
|
||||
|
|
@ -95,9 +92,9 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
|
|||
//if possibleParent in parents, put first
|
||||
//use sort but use any instead of contains because contains uses == and we want to compare by id
|
||||
possibleParents.sort((a, b) {
|
||||
if (parents.any((parent) => parent.id == a.id)) {
|
||||
if (parent?.id == a.id) {
|
||||
return -1;
|
||||
} else if (parents.any((parent) => parent.id == b.id)) {
|
||||
} else if (parent?.id == b.id) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.name.compareTo(b.name);
|
||||
|
|
@ -109,18 +106,15 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
|
|||
|
||||
Future<void> _addSingleSpace(String roomToAddId, Room newParent) async {
|
||||
GoogleAnalytics.addParent(roomToAddId, newParent.classCode);
|
||||
await newParent.setSpaceChild(
|
||||
await newParent.pangeaSetSpaceChild(
|
||||
roomToAddId,
|
||||
suggested: isSuggested,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> addSpaces(String roomToAddId) async {
|
||||
final List<Future<void>> addFutures = [];
|
||||
for (final Room parent in parents) {
|
||||
addFutures.add(_addSingleSpace(roomToAddId, parent));
|
||||
}
|
||||
await addFutures.wait;
|
||||
if (parent == null) return;
|
||||
await _addSingleSpace(roomToAddId, parent!);
|
||||
}
|
||||
|
||||
Future<void> handleAdd(bool add, Room possibleParent) async {
|
||||
|
|
@ -142,11 +136,7 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
|
|||
}
|
||||
|
||||
setState(
|
||||
() => add
|
||||
? parents.add(possibleParent)
|
||||
: parents.removeWhere(
|
||||
(parent) => parent.id == possibleParent.id,
|
||||
),
|
||||
() => add ? parent = possibleParent : parent = null,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -164,7 +154,7 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
|
|||
SwitchListTile.adaptive(
|
||||
title: possibleParent.nameAndRoomTypeIcon(),
|
||||
activeColor: AppConfig.activeToggleColor,
|
||||
value: parents.any((r) => r.id == possibleParent.id),
|
||||
value: parent?.id == possibleParent.id,
|
||||
onChanged: (bool add) => canAdd
|
||||
? handleAdd(add, possibleParent)
|
||||
: ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
|
|
|||
|
|
@ -43,8 +43,7 @@ class _JoinClassWithLinkState extends State<JoinClassWithLink> {
|
|||
await _pangeaController.pStoreService.save(
|
||||
PLocalKey.cachedClassCodeToJoin,
|
||||
classCode,
|
||||
addClientIdToKey: false,
|
||||
local: true,
|
||||
isAccountData: false,
|
||||
);
|
||||
context.go("/home");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/constants/local.key.dart';
|
||||
import 'package:fluffychat/pangea/enum/span_data_type.dart';
|
||||
import 'package:fluffychat/pangea/models/span_data.dart';
|
||||
import 'package:fluffychat/pangea/utils/bot_style.dart';
|
||||
|
|
@ -154,21 +153,18 @@ class WordMatchContent extends StatelessWidget {
|
|||
.selected = true;
|
||||
|
||||
controller.setState(
|
||||
() => (
|
||||
controller.currentExpression =
|
||||
controller
|
||||
.widget
|
||||
.scm
|
||||
.choreographer
|
||||
.igc
|
||||
.igcTextData
|
||||
!.matches[controller.widget.scm.matchIndex]
|
||||
.match
|
||||
.choices![index]
|
||||
.isBestCorrection
|
||||
() => (controller.currentExpression = controller
|
||||
.widget
|
||||
.scm
|
||||
.choreographer
|
||||
.igc
|
||||
.igcTextData!
|
||||
.matches[controller.widget.scm.matchIndex]
|
||||
.match
|
||||
.choices![index]
|
||||
.isBestCorrection
|
||||
? BotExpression.gold
|
||||
: BotExpression.surprised
|
||||
),
|
||||
: BotExpression.surprised),
|
||||
);
|
||||
// if (controller.widget.scm.pangeaMatch.match.choices![index].type ==
|
||||
// SpanChoiceType.distractor) {
|
||||
|
|
@ -344,6 +340,12 @@ class WordMatchContent extends StatelessWidget {
|
|||
if (controller.widget.scm.pangeaMatch!.isITStart)
|
||||
DontShowSwitchListTile(
|
||||
controller: pangeaController,
|
||||
onSwitch: (bool value) {
|
||||
pangeaController.userController.updateProfile((profile) {
|
||||
profile.userSettings.itAutoPlay = value;
|
||||
return profile;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
@ -486,10 +488,12 @@ class StartITButton extends StatelessWidget {
|
|||
|
||||
class DontShowSwitchListTile extends StatefulWidget {
|
||||
final PangeaController controller;
|
||||
final Function(bool) onSwitch;
|
||||
|
||||
const DontShowSwitchListTile({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.onSwitch,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -510,12 +514,9 @@ class DontShowSwitchListTileState extends State<DontShowSwitchListTile> {
|
|||
activeColor: AppConfig.activeToggleColor,
|
||||
title: Text(L10n.of(context)!.interactiveTranslatorAutoPlaySliderHeader),
|
||||
value: switchValue,
|
||||
onChanged: (value) => {
|
||||
widget.controller.pStoreService.save(
|
||||
PLocalKey.itAutoPlay.toString(),
|
||||
value,
|
||||
),
|
||||
setState(() => switchValue = value),
|
||||
onChanged: (value) {
|
||||
widget.onSwitch(value);
|
||||
setState(() => switchValue = value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:country_picker/country_picker.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/utils/country_display.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
|
||||
import '../../models/user_model.dart';
|
||||
|
||||
|
|
@ -19,28 +16,27 @@ class CountryPickerTile extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Profile? profile = pangeaController.userController.userModel?.profile;
|
||||
final Profile profile = pangeaController.userController.profile;
|
||||
|
||||
final String displayName = CountryDisplayUtil.countryDisplayName(
|
||||
profile.userSettings.country,
|
||||
context,
|
||||
) ??
|
||||
'';
|
||||
|
||||
final String flag = CountryDisplayUtil.flagEmoji(
|
||||
profile.userSettings.country,
|
||||
);
|
||||
|
||||
return ListTile(
|
||||
title: Text(
|
||||
"${L10n.of(context)!.countryInformation}: ${profile?.countryDisplayName(context) ?? ''} ${profile?.flagEmoji}",
|
||||
"${L10n.of(context)!.countryInformation}: $displayName $flag",
|
||||
),
|
||||
trailing: const Icon(Icons.edit_outlined),
|
||||
onTap: () => showCountryPicker(
|
||||
context: context,
|
||||
showPhoneCode:
|
||||
false, // optional. Shows phone code before the country name.
|
||||
onSelect: (Country country) async {
|
||||
showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
try {
|
||||
learningController.changeCountry(country);
|
||||
} catch (err) {
|
||||
debugger(when: kDebugMode);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
showPhoneCode: false,
|
||||
onSelect: learningController.changeCountry,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ import '../../../widgets/matrix.dart';
|
|||
import 'p_language_dropdown.dart';
|
||||
import 'p_question_container.dart';
|
||||
|
||||
pLanguageDialog(BuildContext parentContext, Function callback) async {
|
||||
Future<void> pLanguageDialog(
|
||||
BuildContext parentContext,
|
||||
Function callback,
|
||||
) async {
|
||||
final PangeaController pangeaController = MatrixState.pangeaController;
|
||||
//PTODO: if source language not set by user, default to languge from device settings
|
||||
final LanguageModel? userL1 = pangeaController.languageController.userL1;
|
||||
|
|
@ -88,13 +91,14 @@ pLanguageDialog(BuildContext parentContext, Function callback) async {
|
|||
context: context,
|
||||
future: () async {
|
||||
try {
|
||||
await pangeaController.userController
|
||||
.updateUserProfile(
|
||||
sourceLanguage:
|
||||
selectedSourceLanguage.langCode,
|
||||
targetLanguage:
|
||||
selectedTargetLanguage.langCode,
|
||||
);
|
||||
pangeaController.userController
|
||||
.updateProfile((profile) {
|
||||
profile.userSettings.sourceLanguage =
|
||||
selectedSourceLanguage.langCode;
|
||||
profile.userSettings.targetLanguage =
|
||||
selectedTargetLanguage.langCode;
|
||||
return profile;
|
||||
});
|
||||
Navigator.pop(context);
|
||||
} catch (err, s) {
|
||||
debugger(when: kDebugMode);
|
||||
|
|
|
|||
|
|
@ -1,45 +1,37 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PSettingsSwitchListTile extends StatefulWidget {
|
||||
class ProfileSettingsSwitchListTile extends StatefulWidget {
|
||||
final bool defaultValue;
|
||||
final String pStoreKey;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final bool local;
|
||||
final Function(bool) onChange;
|
||||
|
||||
const PSettingsSwitchListTile.adaptive({
|
||||
const ProfileSettingsSwitchListTile.adaptive({
|
||||
super.key,
|
||||
this.defaultValue = false,
|
||||
required this.pStoreKey,
|
||||
required this.defaultValue,
|
||||
required this.title,
|
||||
required this.onChange,
|
||||
this.subtitle,
|
||||
this.local = false,
|
||||
});
|
||||
|
||||
@override
|
||||
PSettingsSwitchListTileState createState() => PSettingsSwitchListTileState();
|
||||
}
|
||||
|
||||
class PSettingsSwitchListTileState extends State<PSettingsSwitchListTile> {
|
||||
class PSettingsSwitchListTileState
|
||||
extends State<ProfileSettingsSwitchListTile> {
|
||||
bool currentValue = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
currentValue = MatrixState.pangeaController.pStoreService.read(
|
||||
widget.pStoreKey,
|
||||
local: widget.local,
|
||||
) ??
|
||||
widget.defaultValue;
|
||||
currentValue = widget.defaultValue;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final PangeaController pangeaController = MatrixState.pangeaController;
|
||||
return SwitchListTile.adaptive(
|
||||
value: currentValue,
|
||||
title: Text(widget.title),
|
||||
|
|
@ -47,20 +39,15 @@ class PSettingsSwitchListTileState extends State<PSettingsSwitchListTile> {
|
|||
subtitle: widget.subtitle != null ? Text(widget.subtitle!) : null,
|
||||
onChanged: (bool newValue) async {
|
||||
try {
|
||||
await pangeaController.pStoreService.save(
|
||||
widget.pStoreKey,
|
||||
newValue,
|
||||
local: widget.local,
|
||||
);
|
||||
currentValue = newValue;
|
||||
widget.onChange(newValue);
|
||||
setState(() => currentValue = newValue);
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
m: "Failed to updates user setting ${widget.pStoreKey}",
|
||||
m: "Failed to updates user setting",
|
||||
s: s,
|
||||
);
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,6 +132,19 @@ abstract class ClientManager {
|
|||
customImageResizer: PlatformInfos.isMobile ? customImageResizer : null,
|
||||
defaultNetworkRequestTimeout: const Duration(minutes: 30),
|
||||
enableDehydratedDevices: true,
|
||||
// #Pangea
|
||||
syncFilter: Filter(
|
||||
room: RoomFilter(
|
||||
state: StateFilter(lazyLoadMembers: true),
|
||||
timeline: StateFilter(
|
||||
notTypes: [
|
||||
PangeaEventTypes.construct,
|
||||
PangeaEventTypes.summaryAnalytics,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// Pangea#
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,57 +11,67 @@ class ErrorReporter {
|
|||
void onErrorCallback(Object error, [StackTrace? stackTrace]) async {
|
||||
Logs().e(message ?? 'Error caught', error, stackTrace);
|
||||
// #Pangea
|
||||
// Attempt to retrieve the L10n instance using the current context
|
||||
final L10n? l10n = L10n.of(context);
|
||||
|
||||
// Check if the L10n instance is null
|
||||
if (l10n == null) {
|
||||
// Log an error message saying that the localization object is null
|
||||
Logs().e('Localization object is null, cannot show error message.');
|
||||
// Exits early to prevent further execution
|
||||
return;
|
||||
}
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
L10n.of(context)!.oopsSomethingWentWrong,
|
||||
l10n.oopsSomethingWentWrong, // Use the non-null L10n instance to get the error message
|
||||
),
|
||||
),
|
||||
);
|
||||
// final text = '$error\n${stackTrace ?? ''}';
|
||||
// await showAdaptiveDialog(
|
||||
// context: context,
|
||||
// builder: (context) => AlertDialog.adaptive(
|
||||
// title: Text(L10n.of(context)!.reportErrorDescription),
|
||||
// content: SizedBox(
|
||||
// height: 256,
|
||||
// width: 256,
|
||||
// child: SingleChildScrollView(
|
||||
// child: HighlightView(
|
||||
// text,
|
||||
// language: 'sh',
|
||||
// theme: shadesOfPurpleTheme,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// actions: [
|
||||
// TextButton(
|
||||
// onPressed: () => Navigator.of(context).pop(),
|
||||
// child: Text(L10n.of(context)!.close),
|
||||
// ),
|
||||
// TextButton(
|
||||
// onPressed: () => Clipboard.setData(
|
||||
// ClipboardData(text: text),
|
||||
// ),
|
||||
// child: Text(L10n.of(context)!.copy),
|
||||
// ),
|
||||
// TextButton(
|
||||
// onPressed: () => launchUrl(
|
||||
// AppConfig.newIssueUrl.resolveUri(
|
||||
// Uri(
|
||||
// queryParameters: {
|
||||
// 'template': 'bug_report.yaml',
|
||||
// 'title': '[BUG]: ${message ?? error.toString()}',
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// mode: LaunchMode.externalApplication,
|
||||
// ),
|
||||
// child: Text(L10n.of(context)!.report),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// Pangea#
|
||||
}
|
||||
// final text = '$error\n${stackTrace ?? ''}';
|
||||
// await showAdaptiveDialog(
|
||||
// context: context,
|
||||
// builder: (context) => AlertDialog.adaptive(
|
||||
// title: Text(L10n.of(context)!.reportErrorDescription),
|
||||
// content: SizedBox(
|
||||
// height: 256,
|
||||
// width: 256,
|
||||
// child: SingleChildScrollView(
|
||||
// child: HighlightView(
|
||||
// text,
|
||||
// language: 'sh',
|
||||
// theme: shadesOfPurpleTheme,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// actions: [
|
||||
// TextButton(
|
||||
// onPressed: () => Navigator.of(context).pop(),
|
||||
// child: Text(L10n.of(context)!.close),
|
||||
// ),
|
||||
// TextButton(
|
||||
// onPressed: () => Clipboard.setData(
|
||||
// ClipboardData(text: text),
|
||||
// ),
|
||||
// child: Text(L10n.of(context)!.copy),
|
||||
// ),
|
||||
// TextButton(
|
||||
// onPressed: () => launchUrl(
|
||||
// AppConfig.newIssueUrl.resolveUri(
|
||||
// Uri(
|
||||
// queryParameters: {
|
||||
// 'template': 'bug_report.yaml',
|
||||
// 'title': '[BUG]: ${message ?? error.toString()}',
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// mode: LaunchMode.externalApplication,
|
||||
// ),
|
||||
// child: Text(L10n.of(context)!.report),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// Pangea#
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ extension IsStateExtension on Event {
|
|||
}.contains(type);
|
||||
|
||||
// #Pangea
|
||||
// we're filtering out some state events that we don't want to render
|
||||
static const Set<String> importantStateEvents = {
|
||||
EventTypes.Encryption,
|
||||
EventTypes.RoomCreate,
|
||||
|
|
|
|||
|
|
@ -342,8 +342,11 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
|||
} else {
|
||||
// #Pangea
|
||||
if (state == LoginState.loggedIn) {
|
||||
await (await pangeaController.userController.completer).future;
|
||||
await pangeaController.subscriptionController.reinitialize();
|
||||
final futures = [
|
||||
pangeaController.userController.reinitialize(),
|
||||
pangeaController.subscriptionController.reinitialize(),
|
||||
];
|
||||
await Future.wait(futures);
|
||||
}
|
||||
String routeDestination;
|
||||
if (state == LoginState.loggedIn) {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue