Merge pull request #3210 from pangeachat/3163-expand-the-box-for-long-message
chore: allow audio transcript to exapnd outside of original message b…
This commit is contained in:
commit
8cced95abe
6 changed files with 137 additions and 78 deletions
|
|
@ -44,7 +44,6 @@ abstract class AppConfig {
|
|||
toolbarButtonsHeight +
|
||||
(chatInputRowOverlayPadding * 2) +
|
||||
toolbarSpacing;
|
||||
static const double audioTranscriptionMaxHeight = 150.0;
|
||||
|
||||
static TextStyle messageTextStyle(
|
||||
Event? event,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ class PhoneticTranscriptionWidget extends StatefulWidget {
|
|||
|
||||
final bool enabled;
|
||||
|
||||
final VoidCallback? onTranscriptionFetched;
|
||||
|
||||
const PhoneticTranscriptionWidget({
|
||||
super.key,
|
||||
required this.text,
|
||||
|
|
@ -30,6 +32,7 @@ class PhoneticTranscriptionWidget extends StatefulWidget {
|
|||
this.iconSize,
|
||||
this.iconColor,
|
||||
this.enabled = true,
|
||||
this.onTranscriptionFetched,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -103,7 +106,12 @@ class _PhoneticTranscriptionWidgetState
|
|||
},
|
||||
);
|
||||
} finally {
|
||||
if (mounted) setState(() => _isLoading = false);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
widget.onTranscriptionFetched?.call();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -101,6 +101,8 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
bool showSpeechTranslation = false;
|
||||
String? speechTranslation;
|
||||
|
||||
final StreamController contentChangedStream = StreamController.broadcast();
|
||||
|
||||
double maxWidth = AppConfig.toolbarMinWidth;
|
||||
|
||||
/////////////////////////////////////
|
||||
|
|
@ -121,6 +123,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => widget.chatController.clearSelectedEvents(),
|
||||
);
|
||||
contentChangedStream.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -587,7 +590,10 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
|
||||
void setTranslation(String value) {
|
||||
if (mounted) {
|
||||
setState(() => translation = value);
|
||||
setState(() {
|
||||
translation = value;
|
||||
contentChangedStream.add(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -598,12 +604,18 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
}
|
||||
|
||||
if (showTranslation == show) return;
|
||||
setState(() => showTranslation = show);
|
||||
setState(() {
|
||||
showTranslation = show;
|
||||
contentChangedStream.add(true);
|
||||
});
|
||||
}
|
||||
|
||||
void setSpeechTranslation(String value) {
|
||||
if (mounted) {
|
||||
setState(() => speechTranslation = value);
|
||||
setState(() {
|
||||
speechTranslation = value;
|
||||
contentChangedStream.add(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -614,7 +626,10 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
}
|
||||
|
||||
if (showSpeechTranslation == show) return;
|
||||
setState(() => showSpeechTranslation = show);
|
||||
setState(() {
|
||||
showSpeechTranslation = show;
|
||||
contentChangedStream.add(true);
|
||||
});
|
||||
}
|
||||
|
||||
void setTranscription(SpeechToTextModel value) {
|
||||
|
|
@ -622,13 +637,17 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
setState(() {
|
||||
transcriptionError = null;
|
||||
transcription = value;
|
||||
contentChangedStream.add(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void setTranscriptionError(String value) {
|
||||
if (mounted) {
|
||||
setState(() => transcriptionError = value);
|
||||
setState(() {
|
||||
transcriptionError = value;
|
||||
contentChangedStream.add(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
Offset? _currentOffset;
|
||||
|
||||
StreamSubscription? _reactionSubscription;
|
||||
StreamSubscription? _contentChangedSubscription;
|
||||
|
||||
final _animationDuration = const Duration(
|
||||
milliseconds: AppConfig.overlayAnimationDuration,
|
||||
|
|
@ -106,6 +107,10 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
},
|
||||
).listen((_) => setState(() {}));
|
||||
|
||||
_contentChangedSubscription = widget
|
||||
.overlayController.contentChangedStream.stream
|
||||
.listen(_onContentSizeChanged);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await _centeredMessageCompleter.future;
|
||||
if (!mounted) return;
|
||||
|
|
@ -138,6 +143,7 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
void dispose() {
|
||||
_animationController.dispose();
|
||||
_reactionSubscription?.cancel();
|
||||
_contentChangedSubscription?.cancel();
|
||||
MatrixState.pangeaController.matrixState.audioPlayer
|
||||
?..stop()
|
||||
..dispose();
|
||||
|
|
@ -196,34 +202,9 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
}
|
||||
|
||||
if (mode == ReadingAssistanceMode.selectMode) {
|
||||
_overlayOffsetAnimation = Tween<Offset>(
|
||||
begin: _currentOffset,
|
||||
end: _adjustedOriginalMessageOffset,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
),
|
||||
)..addListener(() {
|
||||
if (mounted) {
|
||||
setState(() => _currentOffset = _overlayOffsetAnimation?.value);
|
||||
}
|
||||
});
|
||||
_resetOffsetAnimation(_adjustedOriginalMessageOffset);
|
||||
} else if (mode == ReadingAssistanceMode.practiceMode) {
|
||||
_overlayOffsetAnimation = Tween<Offset>(
|
||||
begin: _currentOffset,
|
||||
end: _centeredMessageOffset!,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
),
|
||||
)..addListener(() {
|
||||
if (mounted) {
|
||||
setState(() => _currentOffset = _overlayOffsetAnimation?.value);
|
||||
}
|
||||
});
|
||||
|
||||
_resetOffsetAnimation(_centeredMessageOffset!);
|
||||
_messageSizeAnimation = Tween<Size>(
|
||||
begin: Size(
|
||||
_originalMessageSize.width,
|
||||
|
|
@ -244,6 +225,40 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
}
|
||||
}
|
||||
|
||||
void _onContentSizeChanged(_) {
|
||||
Future.delayed(FluffyThemes.animationDuration, () {
|
||||
final offset = _overlayMessageRenderBox?.localToGlobal(Offset.zero);
|
||||
if (offset == null || !_overlayMessageRenderBox!.hasSize) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final newOffset = _adjustedMessageOffset(
|
||||
_overlayMessageRenderBox!.size,
|
||||
offset,
|
||||
);
|
||||
|
||||
if (newOffset == _currentOffset) return;
|
||||
_resetOffsetAnimation(newOffset);
|
||||
_animationController.forward(from: 0);
|
||||
});
|
||||
}
|
||||
|
||||
void _resetOffsetAnimation(Offset offset) {
|
||||
_overlayOffsetAnimation = Tween<Offset>(
|
||||
begin: _currentOffset,
|
||||
end: offset,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
),
|
||||
)..addListener(() {
|
||||
if (mounted) {
|
||||
setState(() => _currentOffset = _overlayOffsetAnimation?.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
T _runWithLogging<T>(
|
||||
Function runner,
|
||||
String errorMessage,
|
||||
|
|
@ -326,6 +341,14 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
null,
|
||||
);
|
||||
|
||||
RenderBox? get _overlayMessageRenderBox => _runWithLogging<RenderBox?>(
|
||||
() => MatrixState.pAnyState.getRenderBox(
|
||||
'overlay_message_${widget.event.eventId}',
|
||||
),
|
||||
"Error getting overlay message render box",
|
||||
null,
|
||||
);
|
||||
|
||||
Size get _defaultMessageSize => const Size(FluffyThemes.columnWidth / 2, 100);
|
||||
|
||||
/// The size of the message in the chat list (as opposed to the expanded size in the center overlay)
|
||||
|
|
@ -394,17 +417,28 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
}
|
||||
|
||||
Offset get _adjustedOriginalMessageOffset {
|
||||
return _adjustedMessageOffset(
|
||||
_originalMessageSize,
|
||||
_originalMessageOffset,
|
||||
);
|
||||
}
|
||||
|
||||
Offset _adjustedMessageOffset(
|
||||
Size messageSize,
|
||||
Offset messageOffset,
|
||||
) {
|
||||
if (_messageRenderBox == null || !_messageRenderBox!.hasSize) {
|
||||
return _defaultMessageOffset;
|
||||
}
|
||||
|
||||
final topOffset = _originalMessageOffset.dy;
|
||||
final bottomOffset = _originalMessageBottomOffset -
|
||||
_reactionsHeight -
|
||||
_selectionButtonsHeight;
|
||||
final topOffset = messageOffset.dy;
|
||||
final bottomOffset =
|
||||
(_mediaQuery!.size.height - topOffset - messageSize.height) -
|
||||
_reactionsHeight -
|
||||
_selectionButtonsHeight;
|
||||
|
||||
final hasHeaderOverflow = topOffset <
|
||||
(_headerHeight + AppConfig.toolbarSpacing + _audioTranscriptionHeight);
|
||||
final hasHeaderOverflow =
|
||||
topOffset < (_headerHeight + AppConfig.toolbarSpacing);
|
||||
final hasFooterOverflow =
|
||||
bottomOffset < (_footerHeight + AppConfig.toolbarSpacing);
|
||||
|
||||
|
|
@ -416,15 +450,12 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
}
|
||||
|
||||
if (hasHeaderOverflow) {
|
||||
final difference = topOffset -
|
||||
(_headerHeight +
|
||||
AppConfig.toolbarSpacing +
|
||||
_audioTranscriptionHeight);
|
||||
final difference = topOffset - (_headerHeight + AppConfig.toolbarSpacing);
|
||||
|
||||
double newBottomOffset = _mediaQuery!.size.height -
|
||||
_originalMessageOffset.dy +
|
||||
topOffset +
|
||||
difference -
|
||||
_originalMessageSize.height -
|
||||
messageSize.height -
|
||||
_selectionButtonsHeight;
|
||||
|
||||
if (newBottomOffset < _footerHeight + AppConfig.toolbarSpacing) {
|
||||
|
|
@ -524,12 +555,6 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
return showSelectionButtons ? AppConfig.toolbarButtonsHeight : 0;
|
||||
}
|
||||
|
||||
double get _audioTranscriptionHeight {
|
||||
return widget.pangeaMessageEvent?.isAudioMessage ?? false
|
||||
? AppConfig.audioTranscriptionMaxHeight
|
||||
: 0;
|
||||
}
|
||||
|
||||
bool get _hasReactions {
|
||||
final reactionsEvents = widget.event.aggregatedEvents(
|
||||
widget.chatController.timeline!,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dar
|
|||
import 'package:fluffychat/pangea/toolbar/widgets/measure_render_box.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/overlay_message.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class OverlayCenterContent extends StatelessWidget {
|
||||
final Event event;
|
||||
|
|
@ -69,6 +70,9 @@ class OverlayCenterContent extends StatelessWidget {
|
|||
MeasureRenderBox(
|
||||
onChange: onChangeMessageSize,
|
||||
child: OverlayMessage(
|
||||
key: MatrixState.pAnyState
|
||||
.layerLinkAndKey('overlay_message_${event.eventId}')
|
||||
.key,
|
||||
event,
|
||||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
immersionMode: chatController.choreographer.immersionMode,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/events/message_content.dart';
|
||||
|
|
@ -149,9 +150,8 @@ class OverlayMessage extends StatelessWidget {
|
|||
|
||||
final transcription = showTranscription
|
||||
? Container(
|
||||
width: messageWidth,
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: AppConfig.audioTranscriptionMaxHeight,
|
||||
maxWidth: FluffyThemes.columnWidth * 1.5,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
|
|
@ -178,6 +178,7 @@ class OverlayMessage extends StatelessWidget {
|
|||
child: Column(
|
||||
spacing: 8.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SttTranscriptTokens(
|
||||
model: overlayController.transcription!,
|
||||
|
|
@ -208,6 +209,9 @@ class OverlayMessage extends StatelessWidget {
|
|||
iconColor: textColor,
|
||||
enabled:
|
||||
event.senderId != BotName.byEnvironment,
|
||||
onTranscriptionFetched: () =>
|
||||
overlayController.contentChangedStream
|
||||
.add(true),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -226,9 +230,8 @@ class OverlayMessage extends StatelessWidget {
|
|||
|
||||
final translation = showTranslation || showSpeechTranslation
|
||||
? Container(
|
||||
width: messageWidth,
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: AppConfig.audioTranscriptionMaxHeight,
|
||||
maxWidth: FluffyThemes.columnWidth * 1.5,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
|
|
@ -271,8 +274,6 @@ class OverlayMessage extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
if (readingAssistanceMode == ReadingAssistanceMode.transitionMode)
|
||||
transcription,
|
||||
if (event.relationshipType == RelationshipTypes.reply)
|
||||
FutureBuilder<Event?>(
|
||||
future: event.getReplyEvent(
|
||||
|
|
@ -371,8 +372,6 @@ class OverlayMessage extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
if (readingAssistanceMode == ReadingAssistanceMode.transitionMode)
|
||||
translation,
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -386,26 +385,31 @@ class OverlayMessage extends StatelessWidget {
|
|||
color: noBubble ? Colors.transparent : color,
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (readingAssistanceMode != ReadingAssistanceMode.transitionMode)
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 1.5,
|
||||
maxHeight: maxHeight,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
transcription,
|
||||
sizeAnimation != null
|
||||
? AnimatedBuilder(
|
||||
animation: sizeAnimation!,
|
||||
builder: (context, child) {
|
||||
return SizedBox(
|
||||
height: sizeAnimation!.value.height,
|
||||
width: sizeAnimation!.value.width,
|
||||
child: content,
|
||||
);
|
||||
},
|
||||
)
|
||||
: content,
|
||||
if (readingAssistanceMode != ReadingAssistanceMode.transitionMode)
|
||||
sizeAnimation != null
|
||||
? AnimatedBuilder(
|
||||
animation: sizeAnimation!,
|
||||
builder: (context, child) {
|
||||
return SizedBox(
|
||||
height: sizeAnimation!.value.height,
|
||||
width: sizeAnimation!.value.width,
|
||||
child: content,
|
||||
);
|
||||
},
|
||||
)
|
||||
: content,
|
||||
translation,
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue