Merge branch 'main' into ls-in-detection-and-tokens
This commit is contained in:
commit
f9e1f65dd0
23 changed files with 131 additions and 52 deletions
|
|
@ -4365,5 +4365,6 @@
|
|||
"chooseVoice": "Choose a voice",
|
||||
"enterLanguageLevel": "Please enter a language level",
|
||||
"enterDiscussionTopic": "Please enter a discussion topic",
|
||||
"selectBotChatMode": "Select chat mode"
|
||||
"selectBotChatMode": "Select chat mode",
|
||||
"messageNotInTargetLang": "Message not in target language"
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/controllers/language_list_controller.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
|
||||
|
|
@ -100,6 +101,19 @@ Future<void> startGui(List<Client> clients, SharedPreferences store) async {
|
|||
await firstClient?.accountDataLoading;
|
||||
|
||||
ErrorWidget.builder = (details) => FluffyChatErrorWidget(details);
|
||||
|
||||
// #Pangea
|
||||
// errors seems to happen a lot when users switch better production / staging
|
||||
// while testing by accident. If the account is a production account but server is
|
||||
// staging or vice versa, logout.
|
||||
if (firstClient?.userID?.domain != null) {
|
||||
final isStagingUser = firstClient!.userID!.domain!.contains("staging");
|
||||
final isStagingServer = Environment.isStaging;
|
||||
if (isStagingServer != isStagingUser) {
|
||||
await firstClient.logout();
|
||||
}
|
||||
}
|
||||
// Pangea#
|
||||
runApp(FluffyChatApp(clients: clients, pincode: pin, store: store));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1419,7 +1419,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
|
||||
void onSelectMessage(Event event) {
|
||||
// #Pangea
|
||||
if (choreographer.itController.isOpen) {
|
||||
if (choreographer.itController.willOpen) {
|
||||
return;
|
||||
}
|
||||
// Pangea#
|
||||
|
|
|
|||
|
|
@ -321,6 +321,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
// #Pangea
|
||||
// hintText: L10n.of(context)!.writeAMessage,
|
||||
hintText: hintText(),
|
||||
disabledBorder: InputBorder.none,
|
||||
// Pangea#
|
||||
hintMaxLines: 1,
|
||||
border: InputBorder.none,
|
||||
|
|
|
|||
|
|
@ -478,6 +478,8 @@ class InputBar extends StatelessWidget {
|
|||
// builder: (context, controller, focusNode) => TextField(
|
||||
builder: (context, _, focusNode) => TextField(
|
||||
enableSuggestions: false,
|
||||
readOnly:
|
||||
controller != null && controller!.choreographer.isRunningIT,
|
||||
// Pangea#
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class Choreographer {
|
|||
}
|
||||
|
||||
void send(BuildContext context) {
|
||||
if (isFetching) return;
|
||||
if (!canSendMessage) return;
|
||||
|
||||
if (pangeaController.subscriptionController.subscriptionStatus ==
|
||||
SubscriptionStatus.showPaywall) {
|
||||
|
|
@ -92,7 +92,7 @@ class Choreographer {
|
|||
}
|
||||
|
||||
Future<void> _sendWithIGC(BuildContext context) async {
|
||||
if (!igc.canSendMessage) {
|
||||
if (!canSendMessage) {
|
||||
igc.showFirstMatch(context);
|
||||
return;
|
||||
}
|
||||
|
|
@ -255,13 +255,16 @@ class Choreographer {
|
|||
}
|
||||
|
||||
startLoading();
|
||||
|
||||
// if getting language assistance after finishing IT,
|
||||
// reset the itController
|
||||
if (choreoMode == ChoreoMode.it &&
|
||||
itController.isTranslationDone &&
|
||||
!onlyTokensAndLanguageDetection) {
|
||||
// debugger(when: kDebugMode);
|
||||
itController.clear();
|
||||
}
|
||||
|
||||
await (choreoMode == ChoreoMode.it && !itController.isTranslationDone
|
||||
await (isRunningIT
|
||||
? itController.getTranslationData(_useCustomInput)
|
||||
: igc.getIGCTextData(
|
||||
onlyTokensAndLanguageDetection: onlyTokensAndLanguageDetection,
|
||||
|
|
@ -418,7 +421,7 @@ class Choreographer {
|
|||
setState();
|
||||
}
|
||||
|
||||
giveInputFocus() {
|
||||
void giveInputFocus() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
chatController.inputFocus.requestFocus();
|
||||
});
|
||||
|
|
@ -478,6 +481,9 @@ class Choreographer {
|
|||
bool get _noChange =>
|
||||
_lastChecked != null && _lastChecked == _textController.text;
|
||||
|
||||
bool get isRunningIT =>
|
||||
choreoMode == ChoreoMode.it && !itController.isTranslationDone;
|
||||
|
||||
void startLoading() {
|
||||
_lastChecked = _textController.text;
|
||||
isFetching = true;
|
||||
|
|
@ -505,8 +511,6 @@ class Choreographer {
|
|||
}
|
||||
}
|
||||
|
||||
bool get showIsError => !itController.isOpen && errorService.isError;
|
||||
|
||||
LayerLinkAndKey get itBarLinkAndKey =>
|
||||
MatrixState.pAnyState.layerLinkAndKey(itBarTransformTargetKey);
|
||||
|
||||
|
|
@ -570,7 +574,7 @@ class Choreographer {
|
|||
return AssistanceState.noMessage;
|
||||
}
|
||||
|
||||
if (igc.igcTextData?.matches.isNotEmpty ?? false) {
|
||||
if ((igc.igcTextData?.matches.isNotEmpty ?? false) || isRunningIT) {
|
||||
return AssistanceState.fetched;
|
||||
}
|
||||
|
||||
|
|
@ -584,4 +588,33 @@ class Choreographer {
|
|||
|
||||
return AssistanceState.complete;
|
||||
}
|
||||
|
||||
bool get canSendMessage {
|
||||
// if there's an error, let them send. we don't want to block them from sending in this case
|
||||
if (errorService.isError) return true;
|
||||
|
||||
// if they're in IT mode, don't let them send
|
||||
if (itEnabled && isRunningIT) return false;
|
||||
|
||||
// if they've turned off IGC then let them send the message when they want
|
||||
if (!isAutoIGCEnabled) return true;
|
||||
|
||||
// if we're in the middle of fetching results, don't let them send
|
||||
if (isFetching) return false;
|
||||
|
||||
// they're supposed to run IGC but haven't yet, don't let them send
|
||||
if (isAutoIGCEnabled && igc.igcTextData == null) return false;
|
||||
|
||||
// if they have relevant matches, don't let them send
|
||||
final hasITMatches =
|
||||
igc.igcTextData!.matches.any((match) => match.isITStart);
|
||||
final hasIGCMatches =
|
||||
igc.igcTextData!.matches.any((match) => !match.isITStart);
|
||||
if ((itEnabled && hasITMatches) || (igcEnabled && hasIGCMatches)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise, let them send
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,18 +192,4 @@ class IgcController {
|
|||
// Not sure why this is here
|
||||
// MatrixState.pAnyState.closeOverlay();
|
||||
}
|
||||
|
||||
bool get canSendMessage {
|
||||
if (choreographer.isFetching) return false;
|
||||
if (igcTextData == null ||
|
||||
choreographer.errorService.isError ||
|
||||
igcTextData!.matches.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !((choreographer.itEnabled &&
|
||||
igcTextData!.matches.any((match) => match.isOutOfTargetMatch)) ||
|
||||
(choreographer.igcEnabled &&
|
||||
igcTextData!.matches.any((match) => !match.isOutOfTargetMatch)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,9 +68,10 @@ class ITController {
|
|||
}
|
||||
|
||||
void closeIT() {
|
||||
//if they close it before completing, just put their text back
|
||||
//PTODO - explore using last itStep
|
||||
choreographer.textController.text = sourceText ?? "";
|
||||
// if the user hasn't gone through any IT steps, reset the text
|
||||
if (completedITSteps.isEmpty && sourceText != null) {
|
||||
choreographer.textController.text = sourceText!;
|
||||
}
|
||||
clear();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../controllers/pangea_controller.dart';
|
||||
import '../controllers/error_service.dart';
|
||||
|
||||
class ChoreographerHasErrorButton extends StatelessWidget {
|
||||
final ChoreoError error;
|
||||
final PangeaController pangeaController;
|
||||
final Choreographer choreographer;
|
||||
|
||||
const ChoreographerHasErrorButton(
|
||||
this.pangeaController,
|
||||
this.error, {
|
||||
this.error,
|
||||
this.choreographer, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -26,6 +26,7 @@ class ChoreographerHasErrorButton extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
);
|
||||
choreographer.errorService.resetError();
|
||||
}
|
||||
},
|
||||
mini: true,
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ class ITFeedbackCardController extends State<ITFeedbackCard> {
|
|||
@override
|
||||
Widget build(BuildContext context) => error == null
|
||||
? ITFeedbackCardView(controller: this)
|
||||
: CardErrorWidget(error: error);
|
||||
: CardErrorWidget(error: error!);
|
||||
}
|
||||
|
||||
class ITFeedbackCardView extends StatelessWidget {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,10 @@ class ChoreographerSendButtonState extends State<ChoreographerSendButton> {
|
|||
color: widget.controller.choreographer.assistanceState
|
||||
.stateColor(context),
|
||||
onPressed: () {
|
||||
widget.controller.choreographer.send(context);
|
||||
widget.controller.choreographer.canSendMessage
|
||||
? widget.controller.choreographer.send(context)
|
||||
: widget.controller.choreographer.igc
|
||||
.showFirstMatch(context);
|
||||
},
|
||||
tooltip: L10n.of(context)!.send,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -66,10 +66,12 @@ extension MessageModeExtension on MessageMode {
|
|||
}
|
||||
}
|
||||
|
||||
bool isValidMode(Event event) {
|
||||
bool shouldShowAsToolbarButton(Event event) {
|
||||
switch (this) {
|
||||
case MessageMode.translation:
|
||||
return event.messageType == MessageTypes.Text;
|
||||
case MessageMode.textToSpeech:
|
||||
return event.messageType == MessageTypes.Text;
|
||||
case MessageMode.definition:
|
||||
return event.messageType == MessageTypes.Text;
|
||||
case MessageMode.speechToText:
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ extension AnalyticsRoomExtension on Room {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!isRoomAdmin) return;
|
||||
if (client.userID == null || !isRoomAdmin) return;
|
||||
final spaceHierarchy = await client.getSpaceHierarchy(
|
||||
id,
|
||||
maxDepth: 1,
|
||||
|
|
|
|||
|
|
@ -76,8 +76,8 @@ class ChatFloatingActionButtonState extends State<ChatFloatingActionButton> {
|
|||
}
|
||||
if (widget.controller.choreographer.errorService.error != null) {
|
||||
return ChoreographerHasErrorButton(
|
||||
widget.controller.pangeaController,
|
||||
widget.controller.choreographer.errorService.error!,
|
||||
widget.controller.choreographer,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,11 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
/// If we don't have any good activities for them, we'll decrease this number
|
||||
static const int neededActivities = 3;
|
||||
int activitiesLeftToComplete = neededActivities;
|
||||
|
||||
bool get messageInUserL2 =>
|
||||
pangeaMessageEvent.messageDisplayLangCode ==
|
||||
MatrixState.pangeaController.languageController.userL2?.langCode;
|
||||
|
||||
PangeaMessageEvent get pangeaMessageEvent => widget._pangeaMessageEvent;
|
||||
|
||||
final TtsController tts = TtsController();
|
||||
|
|
@ -160,6 +165,11 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
toolbarMode = MessageMode.speechToText;
|
||||
return;
|
||||
}
|
||||
// if (!messageInUserL2) {
|
||||
// activitiesLeftToComplete = 0;
|
||||
// toolbarMode = MessageMode.nullMode;
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (activitiesLeftToComplete > 0) {
|
||||
toolbarMode = MessageMode.practiceActivity;
|
||||
|
|
@ -385,6 +395,10 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
}
|
||||
|
||||
Size? get messageSize {
|
||||
if (messageRenderBox == null || !messageRenderBox!.hasSize) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return messageRenderBox?.size;
|
||||
} catch (e, s) {
|
||||
|
|
@ -394,6 +408,10 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
}
|
||||
|
||||
Offset? get messageOffset {
|
||||
if (messageRenderBox == null || !messageRenderBox!.hasSize) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return messageRenderBox?.localToGlobal(Offset.zero);
|
||||
} catch (e, s) {
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ class MessageSpeechToTextCardState extends State<MessageSpeechToTextCard> {
|
|||
// done fetchig but not results means some kind of error
|
||||
if (speechToTextResponse == null) {
|
||||
return CardErrorWidget(
|
||||
error: error,
|
||||
error: error ?? "Failed to fetch speech to text",
|
||||
maxWidth: AppConfig.toolbarMinWidth,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ import 'package:fluffychat/pangea/widgets/chat/message_translation_card.dart';
|
|||
import 'package:fluffychat/pangea/widgets/chat/message_unsubscribed_card.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/widgets/igc/word_data_card.dart';
|
||||
import 'package:fluffychat/pangea/widgets/message_display_card.dart';
|
||||
import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_card.dart';
|
||||
import 'package:fluffychat/pangea/widgets/select_to_define.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';
|
||||
|
||||
const double minCardHeight = 70;
|
||||
|
||||
|
|
@ -32,7 +33,7 @@ class MessageToolbar extends StatelessWidget {
|
|||
required this.tts,
|
||||
});
|
||||
|
||||
Widget get toolbarContent {
|
||||
Widget toolbarContent(BuildContext context) {
|
||||
final bool subscribed =
|
||||
MatrixState.pangeaController.subscriptionController.isSubscribed;
|
||||
|
||||
|
|
@ -42,6 +43,18 @@ class MessageToolbar extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
// Check if the message is in the user's second language
|
||||
final bool messageInUserL2 = pangeaMessageEvent.messageDisplayLangCode ==
|
||||
MatrixState.pangeaController.languageController.userL2?.langCode;
|
||||
|
||||
// If not in the target language show specific messsage
|
||||
if (!messageInUserL2) {
|
||||
return MessageDisplayCard(
|
||||
displayText:
|
||||
L10n.of(context)!.messageNotInTargetLang, // Pass the display text,
|
||||
);
|
||||
}
|
||||
|
||||
switch (overLayController.toolbarMode) {
|
||||
case MessageMode.translation:
|
||||
return MessageTranslationCard(
|
||||
|
|
@ -62,7 +75,9 @@ class MessageToolbar extends StatelessWidget {
|
|||
);
|
||||
case MessageMode.definition:
|
||||
if (!overLayController.isSelection) {
|
||||
return const SelectToDefine();
|
||||
return MessageDisplayCard(
|
||||
displayText: L10n.of(context)!.selectToDefine,
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
final selectedText = overLayController.targetText;
|
||||
|
|
@ -127,7 +142,7 @@ class MessageToolbar extends StatelessWidget {
|
|||
child: SingleChildScrollView(
|
||||
child: AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: toolbarContent,
|
||||
child: toolbarContent(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class ToolbarButtons extends StatelessWidget {
|
|||
overlayController.pangeaMessageEvent;
|
||||
|
||||
List<MessageMode> get modes => MessageMode.values
|
||||
.where((mode) => mode.isValidMode(pangeaMessageEvent.event))
|
||||
.where((mode) => mode.shouldShowAsToolbarButton(pangeaMessageEvent.event))
|
||||
.toList();
|
||||
|
||||
static const double iconWidth = 36.0;
|
||||
|
|
|
|||
|
|
@ -241,6 +241,8 @@ class ConversationBotSettingsDialogState
|
|||
|
||||
updateFromTextControllers();
|
||||
|
||||
Navigator.of(context).pop(botOptions);
|
||||
|
||||
final bool isBotRoomMember =
|
||||
await widget.room.botIsInRoom;
|
||||
if (addBot && !isBotRoomMember) {
|
||||
|
|
@ -248,8 +250,6 @@ class ConversationBotSettingsDialogState
|
|||
} else if (!addBot && isBotRoomMember) {
|
||||
await widget.room.kick(BotName.byEnvironment);
|
||||
}
|
||||
|
||||
Navigator.of(context).pop(botOptions);
|
||||
},
|
||||
child: Text(L10n.of(context)!.confirm),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ import 'package:fluffychat/pangea/widgets/igc/card_header.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class CardErrorWidget extends StatelessWidget {
|
||||
final Object? error;
|
||||
final Object error;
|
||||
final Choreographer? choreographer;
|
||||
final int? offset;
|
||||
final double? maxWidth;
|
||||
|
||||
const CardErrorWidget({
|
||||
super.key,
|
||||
this.error,
|
||||
required this.error,
|
||||
this.choreographer,
|
||||
this.offset,
|
||||
this.maxWidth,
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ class WordDataCardView extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
if (controller.wordNetError != null) {
|
||||
return CardErrorWidget(
|
||||
error: controller.wordNetError,
|
||||
error: controller.wordNetError!,
|
||||
maxWidth: AppConfig.toolbarMinWidth,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import 'package:fluffychat/pangea/utils/bot_style.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
class SelectToDefine extends StatelessWidget {
|
||||
const SelectToDefine({
|
||||
class MessageDisplayCard extends StatelessWidget {
|
||||
final String displayText;
|
||||
|
||||
const MessageDisplayCard({
|
||||
super.key,
|
||||
required this.displayText,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -12,7 +14,7 @@ class SelectToDefine extends StatelessWidget {
|
|||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 20, 16, 16),
|
||||
child: Text(
|
||||
L10n.of(context)!.selectToDefine,
|
||||
displayText,
|
||||
style: BotStyle.text(context),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
|
@ -6,7 +6,7 @@ description: Learn a language while texting your friends.
|
|||
# Pangea#
|
||||
publish_to: none
|
||||
# On version bump also increase the build number for F-Droid
|
||||
version: 1.22.6+3556
|
||||
version: 1.22.9+3559
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue