Merge branch 'main' into speech-to-text

pull/1116/head
William Jordan-Cooley 2 years ago
commit 821fda7365

@ -113,6 +113,14 @@ abstract class AppRoutes {
),
),
// #Pangea
GoRoute(
path: '/join_with_link',
pageBuilder: (context, state) => defaultPageBuilder(
context,
state,
const JoinClassWithLink(),
),
),
GoRoute(
path: '/user_age',
pageBuilder: (context, state) => defaultPageBuilder(
@ -154,15 +162,6 @@ abstract class AppRoutes {
),
redirect: loggedOutRedirect,
),
GoRoute(
path: '/join_with_link',
pageBuilder: (context, state) => defaultPageBuilder(
context,
state,
const JoinClassWithLink(),
),
redirect: loggedOutRedirect,
),
// Pangea#
GoRoute(
path: '/rooms',

@ -575,70 +575,72 @@ class Message extends StatelessWidget {
),
child: container,
),
Positioned(
left: ownMessage ? null : 48,
right: ownMessage ? 4 : null,
top: displayTime ? 38 : 0,
child: AnimatedScale(
duration: Duration(
milliseconds:
(FluffyThemes.animationDuration.inMilliseconds / 2)
.floor(),
),
curve: FluffyThemes.animationCurve,
scale: !longPressSelect && hovered ? 1 : 0,
alignment: Alignment.center,
child: Material(
color: Theme.of(context)
.colorScheme
.secondaryContainer
.withOpacity(0.9),
elevation:
Theme.of(context).appBarTheme.scrolledUnderElevation ??
4,
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
shadowColor: Theme.of(context).appBarTheme.shadowColor,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (event.room.canSendDefaultMessages)
SizedBox(
width: 32,
height: 32,
child: IconButton(
icon: Icon(
Icons.reply_outlined,
size: 16,
color: Theme.of(context)
.colorScheme
.onTertiaryContainer,
),
tooltip: L10n.of(context)!.reply,
onPressed: event.room.canSendDefaultMessages
? () => onSwipe()
: null,
),
),
SizedBox(
width: 32,
height: 32,
child: IconButton(
icon: Icon(
Icons.more_vert,
size: 16,
color: Theme.of(context)
.colorScheme
.onTertiaryContainer,
),
tooltip: L10n.of(context)!.select,
onPressed: () => onSelect(event),
),
),
],
),
),
),
),
// #Pangea
// Positioned(
// left: ownMessage ? null : 48,
// right: ownMessage ? 4 : null,
// top: displayTime ? 38 : 0,
// child: AnimatedScale(
// duration: Duration(
// milliseconds:
// (FluffyThemes.animationDuration.inMilliseconds / 2)
// .floor(),
// ),
// curve: FluffyThemes.animationCurve,
// scale: !longPressSelect && hovered ? 1 : 0,
// alignment: Alignment.center,
// child: Material(
// color: Theme.of(context)
// .colorScheme
// .secondaryContainer
// .withOpacity(0.9),
// elevation:
// Theme.of(context).appBarTheme.scrolledUnderElevation ??
// 4,
// borderRadius: BorderRadius.circular(AppConfig.borderRadius),
// shadowColor: Theme.of(context).appBarTheme.shadowColor,
// child: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// if (event.room.canSendDefaultMessages)
// SizedBox(
// width: 32,
// height: 32,
// child: IconButton(
// icon: Icon(
// Icons.reply_outlined,
// size: 16,
// color: Theme.of(context)
// .colorScheme
// .onTertiaryContainer,
// ),
// tooltip: L10n.of(context)!.reply,
// onPressed: event.room.canSendDefaultMessages
// ? () => onSwipe()
// : null,
// ),
// ),
// SizedBox(
// width: 32,
// height: 32,
// child: IconButton(
// icon: Icon(
// Icons.more_vert,
// size: 16,
// color: Theme.of(context)
// .colorScheme
// .onTertiaryContainer,
// ),
// tooltip: L10n.of(context)!.select,
// onPressed: () => onSelect(event),
// ),
// ),
// ],
// ),
// ),
// ),
// ),
// Pangea#
],
),
),

@ -50,11 +50,11 @@ class InputBar extends StatelessWidget {
List<Map<String, String?>> getSuggestions(String text) {
// #Pangea
final List<Map<String, String?>> ret = <Map<String, String?>>[];
// if (controller!.selection.baseOffset !=
// controller!.selection.extentOffset ||
// controller!.selection.baseOffset < 0) {
// return []; // no entries if there is selected text
// }
if (controller!.selection.baseOffset !=
controller!.selection.extentOffset ||
controller!.selection.baseOffset < 0) {
return []; // no entries if there is selected text
}
// Pangea#
final searchText =
controller!.text.substring(0, controller!.selection.baseOffset);

@ -205,7 +205,7 @@ class ChatListItem extends StatelessWidget {
// Future.value(L10n.of(context)!.emptyChat),
future: room.lastEvent != null
? GetChatListItemSubtitle().getSubtitle(
context,
L10n.of(context)!,
room.lastEvent,
MatrixState.pangeaController,
)

@ -81,17 +81,25 @@ class _SpaceViewState extends State<SpaceView> {
nextBatch: null,
);
}
setState(() {
error = null;
loading = true;
});
// Pangea#
final activeSpaceId = widget.controller.activeSpaceId!;
final client = Matrix.of(context).client;
final activeSpace = client.getRoomById(activeSpaceId);
await activeSpace?.postLoad();
setState(() {
error = null;
loading = true;
});
// #Pangea
// setState(() {
// error = null;
// loading = true;
// });
// Pangea#
try {
final response = await client.getSpaceHierarchy(

@ -86,6 +86,7 @@ class NewGroupView extends StatelessWidget {
// ),
ConversationBotSettings(
key: controller.addConversationBotKey,
activeSpaceId: controller.activeSpaceId,
),
const Divider(height: 1),
AddToSpaceToggles(

@ -52,6 +52,7 @@ class Choreographer {
String? _lastChecked;
ChoreoMode choreoMode = ChoreoMode.igc;
final StreamController stateListener = StreamController();
StreamSubscription? trialStream;
Choreographer(this.pangeaController, this.chatController) {
_initialize();
@ -64,6 +65,9 @@ class Choreographer {
errorService = ErrorService(this);
altTranslator = AlternativeTranslator(this);
_textController.addListener(_onChangeListener);
trialStream = pangeaController
.subscriptionController.trialActivationStream.stream
.listen((_) => _onChangeListener);
clear();
}
@ -215,7 +219,7 @@ class Choreographer {
if (choreoMode == ChoreoMode.it &&
itController.isTranslationDone &&
!tokensOnly) {
debugger(when: kDebugMode);
// debugger(when: kDebugMode);
}
await (choreoMode == ChoreoMode.it && !itController.isTranslationDone
@ -414,6 +418,7 @@ class Choreographer {
dispose() {
_textController.dispose();
trialStream?.cancel();
}
LanguageModel? get l2Lang {

@ -105,36 +105,44 @@ class ClassController extends BaseController {
}
Future<void> joinClasswithCode(BuildContext context, String classCode) async {
final QueryPublicRoomsResponse queryPublicRoomsResponse =
await Matrix.of(context).client.queryPublicRooms(
limit: 1,
filter: PublicRoomQueryFilter(genericSearchTerm: classCode),
);
final PublicRoomsChunk? classChunk =
queryPublicRoomsResponse.chunk.firstWhereOrNull((element) {
return element.canonicalAlias?.replaceAll("#", "").split(":")[0] ==
classCode;
});
if (classChunk == null) {
ClassCodeUtil.messageSnack(context, L10n.of(context)!.unableToFindClass);
return;
}
try {
final QueryPublicRoomsResponse queryPublicRoomsResponse =
await Matrix.of(context).client.queryPublicRooms(
limit: 1,
filter: PublicRoomQueryFilter(genericSearchTerm: classCode),
);
final PublicRoomsChunk? classChunk =
queryPublicRoomsResponse.chunk.firstWhereOrNull((element) {
return element.canonicalAlias?.replaceAll("#", "").split(":")[0] ==
classCode;
});
if (classChunk == null) {
ClassCodeUtil.messageSnack(
context, L10n.of(context)!.unableToFindClass);
return;
}
if (Matrix.of(context)
.client
.rooms
.any((room) => room.id == classChunk.roomId)) {
setActiveSpaceIdInChatListController(classChunk.roomId);
ClassCodeUtil.messageSnack(context, L10n.of(context)!.alreadyInClass);
return;
}
await _pangeaController.matrixState.client.joinRoom(classChunk.roomId);
if (Matrix.of(context)
.client
.rooms
.any((room) => room.id == classChunk.roomId)) {
setActiveSpaceIdInChatListController(classChunk.roomId);
ClassCodeUtil.messageSnack(context, L10n.of(context)!.alreadyInClass);
GoogleAnalytics.joinClass(classCode);
return;
} catch (err) {
ClassCodeUtil.messageSnack(
context,
ErrorCopy(context, err).body,
);
}
await _pangeaController.matrixState.client.joinRoom(classChunk.roomId);
setActiveSpaceIdInChatListController(classChunk.roomId);
GoogleAnalytics.joinClass(classCode);
return;
// P-EPIC
// prereq - server needs ability to invite to private room. how?
// does server api have ability with admin token?

@ -33,9 +33,8 @@ enum CanSendStatus {
class SubscriptionController extends BaseController {
late PangeaController _pangeaController;
SubscriptionInfo? subscription;
bool initialized = false;
final StreamController subscriptionStream = StreamController.broadcast();
final StreamController trialActivationStream = StreamController.broadcast();
SubscriptionController(PangeaController pangeaController) : super() {
_pangeaController = pangeaController;
@ -46,7 +45,28 @@ class SubscriptionController extends BaseController {
(subscription!.currentSubscriptionId != null ||
subscription!.currentSubscription != null);
bool _isInitializing = false;
Completer<void> initialized = Completer<void>();
Future<void> initialize() async {
if (initialized.isCompleted) return;
if (_isInitializing) {
await initialized.future;
return;
}
_isInitializing = true;
await _initialize();
_isInitializing = false;
initialized.complete();
}
Future<void> reinitialize() async {
initialized = Completer<void>();
_isInitializing = false;
await initialize();
}
Future<void> _initialize() async {
try {
if (_pangeaController.matrixState.client.userID == null) {
debugPrint(
@ -64,8 +84,6 @@ class SubscriptionController extends BaseController {
setNewUserTrial();
}
initialized = true;
if (!kIsWeb) {
Purchases.addCustomerInfoUpdateListener(
(CustomerInfo info) async {
@ -174,10 +192,13 @@ class SubscriptionController extends BaseController {
void activateNewUserTrial() {
_pangeaController.pStoreService
.save(
MatrixProfile.activatedFreeTrial.title,
true,
)
.then((_) => setNewUserTrial());
MatrixProfile.activatedFreeTrial.title,
true,
)
.then((_) {
setNewUserTrial();
trialActivationStream.add(true);
});
}
void setNewUserTrial() {
@ -198,6 +219,9 @@ class SubscriptionController extends BaseController {
}
Future<void> updateCustomerInfo() async {
if (!initialized.isCompleted) {
await initialize();
}
if (subscription == null) {
ErrorHandler.logError(
m: "Null subscription info in subscription settings",
@ -234,7 +258,7 @@ class SubscriptionController extends BaseController {
}
bool get _shouldShowPaywall {
return initialized &&
return initialized.isCompleted &&
!isSubscribed &&
(_lastDismissedPaywall == null ||
DateTime.now().difference(_lastDismissedPaywall!).inHours >
@ -265,7 +289,7 @@ class SubscriptionController extends BaseController {
Future<void> showPaywall(BuildContext context) async {
try {
if (!initialized) {
if (!initialized.isCompleted) {
await initialize();
}
if (subscription?.availableSubscriptions.isEmpty ?? true) {

@ -100,7 +100,6 @@ class PangeaMessageEvent {
BuildContext context,
) async {
final String text = (await representationByLanguageGlobal(
context: context,
langCode: langCode,
))
?.text ??
@ -476,7 +475,6 @@ class PangeaMessageEvent {
}
Future<PangeaRepresentation?> representationByLanguageGlobal({
required BuildContext context,
required String langCode,
}) async {
// try {

@ -1,15 +1,13 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:fluffychat/pangea/config/environment.dart';
import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
import 'package:fluffychat/pangea/models/base_subscription_info.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/material.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class MobileSubscriptionInfo extends SubscriptionInfo {
MobileSubscriptionInfo({required super.pangeaController}) : super();
@ -119,11 +117,11 @@ class MobileSubscriptionInfo extends SubscriptionInfo {
Future<void> setCustomerInfo() async {
if (allProducts == null) {
ErrorHandler.logError(
m: "Null appProducts in setCustomerInfo",
m: "Null allProducts in setCustomerInfo",
s: StackTrace.current,
);
debugPrint(
"Null appProducts in setCustomerInfo",
"Null allProducts in setCustomerInfo",
);
return;
}

@ -30,7 +30,7 @@ class SubscriptionManagementController extends State<SubscriptionManagement> {
@override
void initState() {
if (!subscriptionController.initialized) {
if (!subscriptionController.initialized.isCompleted) {
subscriptionController.initialize().then((_) => setState(() {}));
}

@ -1,7 +1,6 @@
import 'dart:math';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
@ -35,18 +34,10 @@ class ClassCodeUtil {
],
);
if (classCode == null || classCode.single.isEmpty) return;
try {
await pangeaController.classController.joinClasswithCode(
context,
classCode.first,
);
} catch (err) {
messageSnack(
context,
ErrorCopy(context, err).body,
);
}
await pangeaController.classController.joinClasswithCode(
context,
classCode.first,
);
}
static messageDialog(

@ -4,7 +4,6 @@ import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/models/class_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
@ -12,16 +11,17 @@ import '../../utils/matrix_sdk_extensions/matrix_locals.dart';
class GetChatListItemSubtitle {
Future<String> getSubtitle(
BuildContext context,
L10n l10n,
Event? event,
PangeaController pangeaController,
) async {
if (event == null) return L10n.of(context)!.emptyChat;
if (event == null) return l10n.emptyChat;
try {
String? eventContextId = event.eventId;
if (!event.eventId.isValidMatrixId || event.eventId.sigil != '\$') {
eventContextId = null;
}
final Timeline timeline =
await event.room.getTimeline(eventContextId: eventContextId);
@ -47,7 +47,7 @@ class GetChatListItemSubtitle {
!pangeaController.permissionsController
.isToolEnabled(ToolSetting.immersionMode, event.room)) {
return event.calcLocalizedBody(
MatrixLocals(L10n.of(context)!),
MatrixLocals(l10n),
hideReply: true,
hideEdit: true,
plaintextBody: true,
@ -71,14 +71,13 @@ class GetChatListItemSubtitle {
final String? text =
(await pangeaMessageEvent.representationByLanguageGlobal(
context: context,
langCode: l2Code,
))
?.text;
final i18n = MatrixLocals(L10n.of(context)!);
final i18n = MatrixLocals(l10n);
if (text == null) return L10n.of(context)!.emptyChat;
if (text == null) return l10n.emptyChat;
if (!event.room.isDirectChat ||
event.room.directChatMatrixID != event.room.lastEvent?.senderId) {
@ -95,7 +94,7 @@ class GetChatListItemSubtitle {
} catch (e, s) {
// debugger(when: kDebugMode);
ErrorHandler.logError(e: e, s: s);
return event?.body ?? L10n.of(context)!.emptyChat;
return event?.body ?? l10n.emptyChat;
}
}
}

@ -283,8 +283,13 @@ class MessageToolbarState extends State<MessageToolbar> {
widget.textSelection.selectionStream.stream.listen((value) {
timer?.cancel();
timer = Timer(const Duration(milliseconds: 500), () {
if (currentMode != null || value != null && value.isNotEmpty) {
updateMode(currentMode ?? MessageMode.translation);
if (value != null && value.isNotEmpty) {
final MessageMode newMode = currentMode == MessageMode.definition
? MessageMode.definition
: MessageMode.translation;
updateMode(newMode);
} else if (currentMode != null) {
updateMode(currentMode!);
}
});
});

@ -52,7 +52,6 @@ class MessageTranslationCardState extends State<MessageTranslationCard> {
if (repEvent == null && mounted) {
repEvent = await widget.messageEvent.representationByLanguageGlobal(
context: context,
langCode: langCode,
);
}

@ -34,7 +34,6 @@ class MessageUnsubscribedCard extends StatelessWidget {
MatrixState.pangeaController.subscriptionController
.showPaywall(context);
}
MatrixState.pAnyState.closeOverlay();
}
return Column(

@ -72,7 +72,7 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
final activeSpace =
Matrix.of(context).client.getRoomById(widget.activeSpaceId!);
if (activeSpace != null && activeSpace.canIAddSpaceChild(null)) {
parents.add(SuggestionStatus(false, activeSpace));
parents.add(SuggestionStatus(true, activeSpace));
} else {
ErrorHandler.logError(
e: Exception('activeSpaceId ${widget.activeSpaceId} not found'),
@ -111,12 +111,13 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
}
}
Future<void> _addSingleSpace(String roomToAddId, Room newParent) {
Future<void> _addSingleSpace(String roomToAddId, Room newParent) async {
GoogleAnalytics.addParent(roomToAddId, newParent.classCode);
return newParent.setSpaceChild(
await newParent.setSpaceChild(
roomToAddId,
suggested: isSuggestedInSpace(newParent),
);
await setSuggested(true, newParent);
}
Future<void> addSpaces(String roomToAddId) async {
@ -147,7 +148,7 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
setState(
() => add
? parents.add(SuggestionStatus(false, possibleParent))
? parents.add(SuggestionStatus(true, possibleParent))
: parents.removeWhere(
(suggestionStatus) =>
suggestionStatus.room.id == possibleParent.id,

@ -1,14 +1,11 @@
import 'package:fluffychat/pangea/constants/url_query_parameter_keys.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/utils/class_code.dart';
import 'package:fluffychat/widgets/layouts/empty_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import '../../../widgets/matrix.dart';
import '../../constants/local.key.dart';
import '../../utils/error_handler.dart';
//if on home with classcode in url and not logged in, then save it soemhow and after llogin, join class automatically
//if on home with classcode in url and logged in, then join class automatically
@ -28,49 +25,31 @@ class _JoinClassWithLinkState extends State<JoinClassWithLink> {
void initState() {
super.initState();
Future.delayed(Duration.zero, () {
Future.delayed(Duration.zero, () async {
classCode = GoRouterState.of(context)
.uri
.queryParameters[UrlQueryParameterKeys.classCode];
if (classCode == null) {
return ClassCodeUtil.messageDialog(
context,
L10n.of(context)!.unableToFindClassCode,
() => context.go("/rooms"),
Sentry.addBreadcrumb(
Breadcrumb(
message:
"Navigated to join_with_link without class code query parameter",
),
);
return;
}
if (!Matrix.of(context).client.isLogged()) {
return ClassCodeUtil.messageDialog(
context, L10n.of(context)!.pleaseLoginFirst, () async {
await _pangeaController.pStoreService.save(
PLocalKey.cachedClassCodeToJoin,
classCode,
addClientIdToKey: false,
local: true,
);
context.go("/home");
});
}
_pangeaController.classController
.joinClasswithCode(
context,
classCode!,
)
.onError(
(error, stackTrace) => ClassCodeUtil.messageSnack(
context,
ErrorCopy(context, error).body,
),
)
.whenComplete(
() => context.go("/rooms"),
);
await _pangeaController.pStoreService.save(
PLocalKey.cachedClassCodeToJoin,
classCode,
addClientIdToKey: false,
local: true,
);
context.go("/home");
});
}
@override
Widget build(BuildContext context) => const EmptyPage();
Widget build(BuildContext context) => const SizedBox();
}

@ -20,12 +20,14 @@ import '../../utils/error_handler.dart';
class ConversationBotSettings extends StatefulWidget {
final Room? room;
final bool startOpen;
final String? activeSpaceId;
// final ClassSettingsModel? initialSettings;
const ConversationBotSettings({
super.key,
this.room,
this.startOpen = false,
this.activeSpaceId,
// this.initialSettings,
});
@ -37,6 +39,7 @@ class ConversationBotSettingsState extends State<ConversationBotSettings> {
late BotOptionsModel botOptions;
late bool isOpen;
bool addBot = false;
Room? parentSpace;
ConversationBotSettingsState({Key? key});
@ -50,6 +53,12 @@ class ConversationBotSettingsState extends State<ConversationBotSettings> {
addBot = isBotRoom;
});
});
parentSpace = widget.activeSpaceId != null
? Matrix.of(context).client.getRoomById(widget.activeSpaceId!)
: null;
if (parentSpace != null && botOptions.languageLevel == null) {
botOptions.languageLevel = parentSpace?.classSettings?.languageLevel;
}
}
Future<void> updateBotOption(void Function() makeLocalChange) async {

@ -87,7 +87,6 @@ class PangeaRichTextState extends State<PangeaRichText> {
widget.pangeaMessageEvent
.representationByLanguageGlobal(
context: context,
langCode: widget.pangeaMessageEvent.messageDisplayLangCode,
)
.onError(

@ -77,9 +77,9 @@ class SpanCardState extends State<SpanCard> {
if (mounted) {
setState(() => fetchingData = false);
}
} catch (e) {
} catch (e, s) {
// debugger(when: kDebugMode);
ErrorHandler.logError(e: e, s: StackTrace.current);
ErrorHandler.logError(e: e, s: s);
if (mounted) {
setState(() {
error = e;

@ -344,6 +344,7 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
// #Pangea
if (state == LoginState.loggedIn) {
await (await pangeaController.userController.completer).future;
await pangeaController.subscriptionController.reinitialize();
}
String routeDestination;
if (state == LoginState.loggedIn) {

Loading…
Cancel
Save