1000 assistance it simplify (#1005)
* gave inline tooltip its own closing logic and closing animation * update inlinetooltips in IT bar * fixes animation weirdness with inline tooltips in IT bar * added learning settings to IT bar * moved language assistance button into chat input row * allow users to send message after click send twice, even if there are still errors
This commit is contained in:
parent
130a011fad
commit
7568469376
11 changed files with 485 additions and 438 deletions
|
|
@ -1,10 +1,12 @@
|
|||
import 'package:animations/animations.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/send_button.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart';
|
||||
import 'package:fluffychat/pangea/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/pangea_reaction_picker.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/avatar.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:material_symbols_icons/symbols.dart';
|
||||
|
|
@ -274,48 +276,54 @@ class ChatInputRow extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: height,
|
||||
width: height,
|
||||
alignment: Alignment.center,
|
||||
child:
|
||||
// #Pangea
|
||||
// KeyBoardShortcuts(
|
||||
// keysToPress: {
|
||||
// LogicalKeyboardKey.altLeft,
|
||||
// LogicalKeyboardKey.keyE,
|
||||
// },
|
||||
// onKeysPressed: controller.emojiPickerAction,
|
||||
// helpLabel: L10n.of(context)!.emojis,
|
||||
// child:
|
||||
// Pangea#
|
||||
IconButton(
|
||||
tooltip: L10n.of(context)!.emojis,
|
||||
icon: PageTransitionSwitcher(
|
||||
transitionBuilder: (
|
||||
Widget child,
|
||||
Animation<double> primaryAnimation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
return SharedAxisTransition(
|
||||
animation: primaryAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
transitionType: SharedAxisTransitionType.scaled,
|
||||
fillColor: Colors.transparent,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
controller.showEmojiPicker
|
||||
? Icons.keyboard
|
||||
: Icons.add_reaction_outlined,
|
||||
key: ValueKey(controller.showEmojiPicker),
|
||||
),
|
||||
),
|
||||
onPressed: controller.emojiPickerAction,
|
||||
),
|
||||
),
|
||||
// #Pangea
|
||||
kIsWeb
|
||||
?
|
||||
// Pangea#
|
||||
Container(
|
||||
height: height,
|
||||
width: height,
|
||||
alignment: Alignment.center,
|
||||
child:
|
||||
// #Pangea
|
||||
// KeyBoardShortcuts(
|
||||
// keysToPress: {
|
||||
// LogicalKeyboardKey.altLeft,
|
||||
// LogicalKeyboardKey.keyE,
|
||||
// },
|
||||
// onKeysPressed: controller.emojiPickerAction,
|
||||
// helpLabel: L10n.of(context)!.emojis,
|
||||
// child:
|
||||
// Pangea#
|
||||
IconButton(
|
||||
tooltip: L10n.of(context)!.emojis,
|
||||
icon: PageTransitionSwitcher(
|
||||
transitionBuilder: (
|
||||
Widget child,
|
||||
Animation<double> primaryAnimation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
return SharedAxisTransition(
|
||||
animation: primaryAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
transitionType:
|
||||
SharedAxisTransitionType.scaled,
|
||||
fillColor: Colors.transparent,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
controller.showEmojiPicker
|
||||
? Icons.keyboard
|
||||
: Icons.add_reaction_outlined,
|
||||
key: ValueKey(controller.showEmojiPicker),
|
||||
),
|
||||
),
|
||||
onPressed: controller.emojiPickerAction,
|
||||
),
|
||||
)
|
||||
// #Pangea
|
||||
: const SizedBox(width: 10),
|
||||
// if (Matrix.of(context).isMultiAccount &&
|
||||
// Matrix.of(context).hasComplexBundles &&
|
||||
// Matrix.of(context).currentBundle!.length > 1)
|
||||
|
|
@ -369,6 +377,11 @@ class ChatInputRow extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
// #Pangea
|
||||
StartIGCButton(
|
||||
controller: controller,
|
||||
),
|
||||
// Pangea#
|
||||
Container(
|
||||
height: height,
|
||||
width: height,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import 'package:fluffychat/pages/chat/chat_event_list.dart';
|
|||
import 'package:fluffychat/pages/chat/pinned_events.dart';
|
||||
import 'package:fluffychat/pages/chat/reply_display.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart';
|
||||
import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/widgets/animations/gain_points.dart';
|
||||
|
|
@ -456,26 +455,18 @@ class ChatView extends StatelessWidget {
|
|||
maxWidth: FluffyThemes.columnWidth * 2.4,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
StartIGCButton(
|
||||
controller: controller,
|
||||
PointsGainedAnimation(
|
||||
gainColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimary,
|
||||
origin:
|
||||
AnalyticsUpdateOrigin.sendMessage,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
PointsGainedAnimation(
|
||||
gainColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimary,
|
||||
origin: AnalyticsUpdateOrigin
|
||||
.sendMessage,
|
||||
),
|
||||
const SizedBox(width: 100),
|
||||
ChatFloatingActionButton(
|
||||
controller: controller,
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 100),
|
||||
ChatFloatingActionButton(
|
||||
controller: controller,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -492,23 +483,38 @@ class ChatView extends StatelessWidget {
|
|||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceContainerHighest,
|
||||
// #Pangea
|
||||
// color: Theme.of(context)
|
||||
// .colorScheme
|
||||
// .surfaceContainerHighest,
|
||||
type: MaterialType.transparency,
|
||||
// Pangea#
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
|
||||
child: Column(
|
||||
children: [
|
||||
const ConnectionStatusHeader(),
|
||||
ITBar(
|
||||
choreographer: controller.choreographer,
|
||||
),
|
||||
ReplyDisplay(controller),
|
||||
ChatInputRowWrapper(
|
||||
controller: controller,
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceContainerHighest,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
ReplyDisplay(controller),
|
||||
ChatInputRowWrapper(
|
||||
controller: controller,
|
||||
),
|
||||
ChatEmojiPicker(controller),
|
||||
],
|
||||
),
|
||||
),
|
||||
ChatEmojiPicker(controller),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ class Choreographer {
|
|||
late ErrorService errorService;
|
||||
|
||||
bool isFetching = false;
|
||||
int _timesClicked = 0;
|
||||
|
||||
Timer? debounceTimer;
|
||||
ChoreoRecord choreoRecord = ChoreoRecord.newRecord;
|
||||
// last checked by IGC or translation
|
||||
|
|
@ -447,6 +449,7 @@ class Choreographer {
|
|||
clear() {
|
||||
choreoMode = ChoreoMode.igc;
|
||||
_lastChecked = null;
|
||||
_timesClicked = 0;
|
||||
isFetching = false;
|
||||
choreoRecord = ChoreoRecord.newRecord;
|
||||
itController.clear();
|
||||
|
|
@ -506,6 +509,18 @@ class Choreographer {
|
|||
setState();
|
||||
}
|
||||
|
||||
void incrementTimesClicked() {
|
||||
if (assistanceState == AssistanceState.fetched) {
|
||||
_timesClicked++;
|
||||
|
||||
// if user is doing IT, call closeIT here to
|
||||
// ensure source text is replaced when needed
|
||||
if (itController.isOpen && _timesClicked > 1) {
|
||||
itController.closeIT();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get roomId => chatController.roomId;
|
||||
|
||||
bool get _useCustomInput => [
|
||||
|
|
@ -602,7 +617,12 @@ class Choreographer {
|
|||
|
||||
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 || l2Lang == null || l1Lang == null) return true;
|
||||
if (errorService.isError ||
|
||||
l2Lang == null ||
|
||||
l1Lang == null ||
|
||||
_timesClicked > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if they're in IT mode, don't let them send
|
||||
if (itEnabled && isRunningIT) return false;
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ import 'package:fluffychat/pangea/constants/choreo_constants.dart';
|
|||
import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
|
||||
import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/utils/inline_tooltip.dart';
|
||||
import 'package:fluffychat/pangea/widgets/animations/gain_points.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../config/app_config.dart';
|
||||
import '../../controllers/it_feedback_controller.dart';
|
||||
import '../../models/it_response_model.dart';
|
||||
import '../../utils/overlay.dart';
|
||||
|
|
@ -31,19 +31,39 @@ class ITBar extends StatefulWidget {
|
|||
ITBarState createState() => ITBarState();
|
||||
}
|
||||
|
||||
class ITBarState extends State<ITBar> {
|
||||
class ITBarState extends State<ITBar> with SingleTickerProviderStateMixin {
|
||||
ITController get itController => widget.choreographer.itController;
|
||||
StreamSubscription? _choreoSub;
|
||||
|
||||
bool showedClickInstruction = false;
|
||||
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _animation;
|
||||
bool wasOpen = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Rebuild the widget each time there's an update from choreo.
|
||||
_choreoSub = widget.choreographer.stateListener.stream.listen((_) {
|
||||
if (itController.willOpen != wasOpen) {
|
||||
itController.willOpen ? _controller.forward() : _controller.reverse();
|
||||
}
|
||||
wasOpen = itController.willOpen;
|
||||
setState(() {});
|
||||
});
|
||||
super.initState();
|
||||
|
||||
wasOpen = itController.willOpen;
|
||||
|
||||
_controller = AnimationController(
|
||||
duration: itController.animationSpeed,
|
||||
vsync: this,
|
||||
);
|
||||
_animation = CurvedAnimation(parent: _controller, curve: Curves.easeInOut);
|
||||
|
||||
// Start in the correct state
|
||||
itController.willOpen ? _controller.forward() : _controller.reverse();
|
||||
}
|
||||
|
||||
bool get showITInstructionsTooltip {
|
||||
|
|
@ -72,129 +92,165 @@ class ITBarState extends State<ITBar> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
final double iconDimension = 36;
|
||||
final double iconSize = 20;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedSize(
|
||||
duration: itController.animationSpeed,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
clipBehavior: Clip.none,
|
||||
child: !itController.willOpen
|
||||
? const SizedBox()
|
||||
: CompositedTransformTarget(
|
||||
link: widget.choreographer.itBarLinkAndKey.link,
|
||||
child: AnimatedOpacity(
|
||||
duration: itController.animationSpeed,
|
||||
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),
|
||||
),
|
||||
),
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(0, 3, 3, 3),
|
||||
child: Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
const Positioned(
|
||||
top: 60,
|
||||
child: PointsGainedAnimation(
|
||||
origin: AnalyticsUpdateOrigin.it,
|
||||
),
|
||||
),
|
||||
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),
|
||||
if (showITInstructionsTooltip)
|
||||
InlineTooltip(
|
||||
instructionsEnum:
|
||||
InstructionsEnum.clickBestOption,
|
||||
onClose: () => setState(() {}),
|
||||
return SizeTransition(
|
||||
sizeFactor: _animation,
|
||||
axis: Axis.vertical,
|
||||
axisAlignment: -1.0,
|
||||
child: CompositedTransformTarget(
|
||||
link: widget.choreographer.itBarLinkAndKey.link,
|
||||
child: Container(
|
||||
key: widget.choreographer.itBarLinkAndKey.key,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).brightness == Brightness.light
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
),
|
||||
padding: const EdgeInsets.fromLTRB(0, 3, 3, 3),
|
||||
child: Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (!itController.isEditingSourceText &&
|
||||
itController.sourceText != null)
|
||||
SizedBox(width: iconDimension * 3),
|
||||
if (!itController.isEditingSourceText)
|
||||
Expanded(
|
||||
child: itController.sourceText != null
|
||||
? Text(
|
||||
itController.sourceText!,
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
: const LinearProgressIndicator(),
|
||||
),
|
||||
if (itController.isEditingSourceText)
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 20,
|
||||
right: 10,
|
||||
top: 10,
|
||||
),
|
||||
if (showTranslationsChoicesTooltip)
|
||||
InlineTooltip(
|
||||
instructionsEnum:
|
||||
InstructionsEnum.translationChoices,
|
||||
onClose: () => setState(() {}),
|
||||
),
|
||||
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,
|
||||
),
|
||||
child: TextField(
|
||||
controller: TextEditingController(
|
||||
text: itController.sourceText,
|
||||
),
|
||||
autofocus: true,
|
||||
enableSuggestions: false,
|
||||
maxLines: null,
|
||||
textInputAction: TextInputAction.send,
|
||||
onSubmitted:
|
||||
itController.onEditSourceTextSubmit,
|
||||
obscureText: false,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!itController.isEditingSourceText &&
|
||||
itController.sourceText != null)
|
||||
SizedBox(
|
||||
width: iconDimension,
|
||||
height: iconDimension,
|
||||
child: IconButton(
|
||||
iconSize: iconSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
onPressed: () {
|
||||
if (itController.nextITStep != null) {
|
||||
itController.setIsEditingSourceText(true);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
// iconSize: 20,
|
||||
),
|
||||
),
|
||||
if (!itController.isEditingSourceText)
|
||||
SizedBox(
|
||||
width: iconDimension,
|
||||
height: iconDimension,
|
||||
child: IconButton(
|
||||
iconSize: iconSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
icon: const Icon(Icons.settings_outlined),
|
||||
onPressed: () => showDialog(
|
||||
context: context,
|
||||
builder: (c) => const SettingsLearning(),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: iconDimension,
|
||||
height: iconDimension,
|
||||
child: IconButton(
|
||||
iconSize: iconSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
icon: const Icon(Icons.close_outlined),
|
||||
onPressed: () {
|
||||
itController.isEditingSourceText
|
||||
? itController.setIsEditingSourceText(false)
|
||||
: itController.closeIT();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
if (showITInstructionsTooltip)
|
||||
const InlineTooltip(
|
||||
instructionsEnum: InstructionsEnum.clickBestOption,
|
||||
),
|
||||
if (showTranslationsChoicesTooltip)
|
||||
const InlineTooltip(
|
||||
instructionsEnum: InstructionsEnum.translationChoices,
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
constraints: const BoxConstraints(minHeight: 80),
|
||||
child: AnimatedSize(
|
||||
duration: itController.animationSpeed,
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// ),
|
||||
),
|
||||
const Positioned(
|
||||
top: 60,
|
||||
child: PointsGainedAnimation(
|
||||
origin: AnalyticsUpdateOrigin.it,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -228,70 +284,6 @@ class ChoiceFeedbackText extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class OriginalText extends StatelessWidget {
|
||||
const OriginalText({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
final ITController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
constraints: const BoxConstraints(minHeight: 50),
|
||||
padding: const EdgeInsets.only(left: 60.0, right: 40.0),
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(AppConfig.borderRadius),
|
||||
topRight: Radius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
//PTODO - does this already update after reset or we need to setState?
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (!controller.isEditingSourceText)
|
||||
controller.sourceText != null
|
||||
? Flexible(child: Text(controller.sourceText!))
|
||||
: const LinearProgressIndicator(),
|
||||
const SizedBox(width: 4),
|
||||
if (controller.isEditingSourceText)
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: TextEditingController(text: controller.sourceText),
|
||||
autofocus: true,
|
||||
enableSuggestions: false,
|
||||
maxLines: null,
|
||||
textInputAction: TextInputAction.send,
|
||||
onSubmitted: controller.onEditSourceTextSubmit,
|
||||
obscureText: false,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!controller.isEditingSourceText && controller.sourceText != null)
|
||||
AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
opacity: controller.nextITStep != null ? 0.7 : 0.0,
|
||||
child: IconButton(
|
||||
onPressed: () => {
|
||||
if (controller.nextITStep != null)
|
||||
{
|
||||
controller.setIsEditingSourceText(true),
|
||||
},
|
||||
},
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
iconSize: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ITChoices extends StatelessWidget {
|
||||
const ITChoices({
|
||||
super.key,
|
||||
|
|
|
|||
|
|
@ -40,26 +40,29 @@ class ChoreographerSendButtonState extends State<ChoreographerSendButton> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.controller.choreographer.isFetching &&
|
||||
widget.controller.choreographer.isAutoIGCEnabled
|
||||
? Container(
|
||||
height: 56,
|
||||
width: 56,
|
||||
padding: const EdgeInsets.all(13),
|
||||
child: const CircularProgressIndicator(),
|
||||
)
|
||||
: Container(
|
||||
height: 56,
|
||||
alignment: Alignment.center,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.send_outlined),
|
||||
color: widget.controller.choreographer.assistanceState
|
||||
.stateColor(context),
|
||||
onPressed: () {
|
||||
widget.controller.choreographer.send(context);
|
||||
},
|
||||
tooltip: L10n.of(context)!.send,
|
||||
),
|
||||
);
|
||||
return
|
||||
// widget.controller.choreographer.isFetching &&
|
||||
// widget.controller.choreographer.isAutoIGCEnabled
|
||||
// ? Container(
|
||||
// height: 56,
|
||||
// width: 56,
|
||||
// padding: const EdgeInsets.all(13),
|
||||
// child: const CircularProgressIndicator(),
|
||||
// )
|
||||
// :
|
||||
Container(
|
||||
height: 56,
|
||||
alignment: Alignment.center,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.send_outlined),
|
||||
color:
|
||||
widget.controller.choreographer.assistanceState.stateColor(context),
|
||||
onPressed: () {
|
||||
widget.controller.choreographer.incrementTimesClicked();
|
||||
widget.controller.choreographer.send(context);
|
||||
},
|
||||
tooltip: L10n.of(context)!.send,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
||||
import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
|
||||
import 'package:fluffychat/pangea/enum/assistance_state_enum.dart';
|
||||
import 'package:fluffychat/pangea/widgets/user_settings/p_language_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import '../../../pages/chat/chat.dart';
|
||||
|
||||
|
|
@ -27,8 +24,8 @@ class StartIGCButtonState extends State<StartIGCButton>
|
|||
AssistanceState get assistanceState =>
|
||||
widget.controller.choreographer.assistanceState;
|
||||
AnimationController? _controller;
|
||||
StreamSubscription? choreoListener;
|
||||
AssistanceState? prevState;
|
||||
StreamSubscription? _choreoListener;
|
||||
AssistanceState? _prevState;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -36,117 +33,96 @@ class StartIGCButtonState extends State<StartIGCButton>
|
|||
vsync: this,
|
||||
duration: const Duration(seconds: 2),
|
||||
);
|
||||
choreoListener = widget.controller.choreographer.stateListener.stream
|
||||
.listen(updateSpinnerState);
|
||||
_choreoListener = widget.controller.choreographer.stateListener.stream
|
||||
.listen(_updateSpinnerState);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.dispose();
|
||||
choreoListener?.cancel();
|
||||
_choreoListener?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void updateSpinnerState(_) {
|
||||
if (prevState != AssistanceState.fetching &&
|
||||
void _updateSpinnerState(_) {
|
||||
if (_prevState != AssistanceState.fetching &&
|
||||
assistanceState == AssistanceState.fetching) {
|
||||
_controller?.repeat();
|
||||
} else if (prevState == AssistanceState.fetching &&
|
||||
} else if (_prevState == AssistanceState.fetching &&
|
||||
assistanceState != AssistanceState.fetching) {
|
||||
_controller?.reset();
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() => prevState = assistanceState);
|
||||
setState(() => _prevState = assistanceState);
|
||||
}
|
||||
}
|
||||
|
||||
bool get itEnabled => widget.controller.choreographer.itEnabled;
|
||||
bool get igcEnabled => widget.controller.choreographer.igcEnabled;
|
||||
void _showFirstMatch() {
|
||||
final igcData = widget.controller.choreographer.igc.igcTextData;
|
||||
if (igcData != null && igcData.matches.isNotEmpty) {
|
||||
widget.controller.choreographer.igc.showFirstMatch(context);
|
||||
}
|
||||
}
|
||||
|
||||
SubscriptionStatus get subscriptionStatus => widget
|
||||
.controller.pangeaController.subscriptionController.subscriptionStatus;
|
||||
|
||||
bool get grammarCorrectionEnabled =>
|
||||
(itEnabled || igcEnabled) &&
|
||||
subscriptionStatus == SubscriptionStatus.subscribed;
|
||||
Future<void> _onTap() async {
|
||||
switch (assistanceState) {
|
||||
case AssistanceState.notFetched:
|
||||
await widget.controller.choreographer.getLanguageHelp(
|
||||
onlyTokensAndLanguageDetection: false,
|
||||
manual: true,
|
||||
);
|
||||
_showFirstMatch();
|
||||
return;
|
||||
case AssistanceState.fetched:
|
||||
_showFirstMatch();
|
||||
return;
|
||||
case AssistanceState.complete:
|
||||
case AssistanceState.fetching:
|
||||
case AssistanceState.noMessage:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!grammarCorrectionEnabled ||
|
||||
widget.controller.choreographer.isAutoIGCEnabled ||
|
||||
widget.controller.choreographer.choreoMode == ChoreoMode.it) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final Widget icon = Icon(
|
||||
Icons.autorenew_rounded,
|
||||
size: 46,
|
||||
color: assistanceState.stateColor(context),
|
||||
);
|
||||
|
||||
return SizedBox(
|
||||
height: 50,
|
||||
width: 50,
|
||||
child: InkWell(
|
||||
onTap: _onTap,
|
||||
customBorder: const CircleBorder(),
|
||||
onLongPress: () => pLanguageDialog(context, () {}),
|
||||
child: FloatingActionButton(
|
||||
tooltip: assistanceState.tooltip(
|
||||
L10n.of(context)!,
|
||||
),
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
disabledElevation: 0,
|
||||
shape: const CircleBorder(),
|
||||
onPressed: () {
|
||||
if (assistanceState != AssistanceState.fetching) {
|
||||
widget.controller.choreographer
|
||||
.getLanguageHelp(
|
||||
onlyTokensAndLanguageDetection: false,
|
||||
manual: true,
|
||||
)
|
||||
.then((_) {
|
||||
if (widget.controller.choreographer.igc.igcTextData != null &&
|
||||
widget.controller.choreographer.igc.igcTextData!.matches
|
||||
.isNotEmpty) {
|
||||
widget.controller.choreographer.igc.showFirstMatch(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
_controller != null
|
||||
? RotationTransition(
|
||||
turns: Tween(begin: 0.0, end: math.pi * 2)
|
||||
.animate(_controller!),
|
||||
child: icon,
|
||||
)
|
||||
: icon,
|
||||
Container(
|
||||
width: 26,
|
||||
height: 26,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
_controller != null
|
||||
? RotationTransition(
|
||||
turns: Tween(begin: 0.0, end: math.pi * 2)
|
||||
.animate(_controller!),
|
||||
child: Icon(
|
||||
size: 36,
|
||||
Icons.autorenew_rounded,
|
||||
color: assistanceState.stateColor(context),
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
size: 36,
|
||||
Icons.autorenew_rounded,
|
||||
color: assistanceState.stateColor(context),
|
||||
),
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
),
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: assistanceState.stateColor(context),
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
size: 16,
|
||||
Icons.check,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
size: 16,
|
||||
Icons.check,
|
||||
color: assistanceState.stateColor(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,75 +1,112 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
class InlineTooltip extends StatelessWidget {
|
||||
class InlineTooltip extends StatefulWidget {
|
||||
final InstructionsEnum instructionsEnum;
|
||||
final VoidCallback onClose;
|
||||
|
||||
const InlineTooltip({
|
||||
super.key,
|
||||
required this.instructionsEnum,
|
||||
required this.onClose,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (instructionsEnum.toggledOff()) {
|
||||
return const SizedBox();
|
||||
}
|
||||
InlineTooltipState createState() => InlineTooltipState();
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
color: Theme.of(context).colorScheme.primary.withAlpha(20),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Lightbulb icon on the left
|
||||
Icon(
|
||||
Icons.lightbulb,
|
||||
size: 20,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// Text in the middle
|
||||
Flexible(
|
||||
child: Center(
|
||||
child: Text(
|
||||
instructionsEnum.body(L10n.of(context)!),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
height: 1.5,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Close button on the right
|
||||
IconButton(
|
||||
constraints: const BoxConstraints(),
|
||||
icon: Icon(
|
||||
Icons.close_outlined,
|
||||
size: 20,
|
||||
class InlineTooltipState extends State<InlineTooltip>
|
||||
with SingleTickerProviderStateMixin {
|
||||
bool _isToggledOff = true;
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _animation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_isToggledOff = widget.instructionsEnum.toggledOff();
|
||||
|
||||
// Initialize AnimationController and Animation
|
||||
_controller = AnimationController(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_animation = CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
|
||||
// Start in correct state
|
||||
if (!_isToggledOff) _controller.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _closeTooltip() {
|
||||
MatrixState.pangeaController.instructions.setToggledOff(
|
||||
widget.instructionsEnum,
|
||||
true,
|
||||
);
|
||||
setState(() {
|
||||
_isToggledOff = true;
|
||||
_controller.reverse();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizeTransition(
|
||||
sizeFactor: _animation,
|
||||
axisAlignment: -1.0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
color: Theme.of(context).colorScheme.primary.withAlpha(20),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.lightbulb,
|
||||
size: _isToggledOff ? 0 : 20,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
onPressed: () {
|
||||
MatrixState.pangeaController.instructions.setToggledOff(
|
||||
instructionsEnum,
|
||||
true,
|
||||
);
|
||||
onClose();
|
||||
},
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Center(
|
||||
child: Text(
|
||||
widget.instructionsEnum.body(L10n.of(context)!),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
height: 1.5,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
constraints: const BoxConstraints(),
|
||||
icon: Icon(
|
||||
Icons.close_outlined,
|
||||
size: _isToggledOff ? 0 : 20,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
onPressed: _closeTooltip,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -187,9 +187,8 @@ class MessageSpeechToTextCardState extends State<MessageSpeechToTextCard> {
|
|||
),
|
||||
],
|
||||
),
|
||||
InlineTooltip(
|
||||
const InlineTooltip(
|
||||
instructionsEnum: InstructionsEnum.speechToText,
|
||||
onClose: () => setState(() => {}),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -156,15 +156,26 @@ class MessageTranslationCardState extends State<MessageTranslationCard> {
|
|||
style: BotStyle.text(context),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (notGoingToTranslate && widget.selection == null)
|
||||
InlineTooltip(
|
||||
instructionsEnum: InstructionsEnum.l1Translation,
|
||||
onClose: () => setState(() {}),
|
||||
if (notGoingToTranslate &&
|
||||
widget.selection == null &&
|
||||
!InstructionsEnum.l1Translation.toggledOff())
|
||||
const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
InlineTooltip(
|
||||
instructionsEnum: InstructionsEnum.l1Translation,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (widget.selection != null)
|
||||
InlineTooltip(
|
||||
instructionsEnum: InstructionsEnum.clickAgainToDeselect,
|
||||
onClose: () => setState(() {}),
|
||||
if (widget.selection != null &&
|
||||
!InstructionsEnum.clickAgainToDeselect.toggledOff())
|
||||
const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
InlineTooltip(
|
||||
instructionsEnum: InstructionsEnum.clickAgainToDeselect,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -67,25 +67,19 @@ class _StarAnimationWidgetState extends State<StarAnimationWidget>
|
|||
}
|
||||
|
||||
class GamifiedTextWidget extends StatelessWidget {
|
||||
final VoidCallback onCloseTooltip;
|
||||
|
||||
const GamifiedTextWidget({
|
||||
required this.onCloseTooltip,
|
||||
super.key,
|
||||
});
|
||||
const GamifiedTextWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
return const SizedBox(
|
||||
width: AppConfig.toolbarMinWidth,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 20, 16, 16),
|
||||
padding: EdgeInsets.fromLTRB(16, 20, 16, 16),
|
||||
child: Column(
|
||||
children: [
|
||||
const StarAnimationWidget(),
|
||||
StarAnimationWidget(),
|
||||
InlineTooltip(
|
||||
instructionsEnum: InstructionsEnum.unlockedLanguageTools,
|
||||
onClose: onCloseTooltip,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -320,14 +320,10 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
|
|||
}
|
||||
}
|
||||
|
||||
void _closeTooltip() => setState(() {});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!fetchingActivity && currentActivity == null) {
|
||||
return GamifiedTextWidget(
|
||||
onCloseTooltip: _closeTooltip,
|
||||
);
|
||||
return const GamifiedTextWidget();
|
||||
}
|
||||
|
||||
return Stack(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue