feat: Directing to click messages with shimmer (#5106)

This commit is contained in:
ggurdin 2026-01-07 11:04:00 -05:00 committed by GitHub
parent af395d0aeb
commit 832533b4f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 275 additions and 225 deletions

View file

@ -1997,6 +1997,17 @@ class ChatController extends State<ChatPageWithRoom>
bool get _isToolbarOpen =>
MatrixState.pAnyState.isOverlayOpen(RegExp(r'^message_toolbar_overlay$'));
bool showMessageShimmer(Event event) {
if (event.type != EventTypes.Message) return false;
if (event.messageType == MessageTypes.Text) {
return !InstructionsEnum.clickTextMessages.isToggledOff;
}
if (event.messageType == MessageTypes.Audio) {
return !InstructionsEnum.clickAudioMessages.isToggledOff;
}
return false;
}
void showToolbar(
Event event, {
PangeaMessageEvent? pangeaMessageEvent,
@ -2060,6 +2071,14 @@ class ChatController extends State<ChatPageWithRoom>
// you've clicked a message so lets turn this off
InstructionsEnum.clickMessage.setToggledOff(true);
if (event.messageType == MessageTypes.Text &&
!InstructionsEnum.clickTextMessages.isToggledOff) {
InstructionsEnum.clickTextMessages.setToggledOff(true);
}
if (event.messageType == MessageTypes.Audio &&
!InstructionsEnum.clickAudioMessages.isToggledOff) {
InstructionsEnum.clickAudioMessages.setToggledOff(true);
}
if (!kIsWeb) {
HapticFeedback.mediumImpact();

View file

@ -34,6 +34,7 @@ class AudioPlayerWidget extends StatefulWidget {
final String senderId;
final PangeaAudioFile? matrixFile;
final bool autoplay;
final bool enableClicks;
// Pangea#
static const int wavesCount = 40;
@ -49,6 +50,7 @@ class AudioPlayerWidget extends StatefulWidget {
required this.senderId,
this.matrixFile,
this.autoplay = false,
this.enableClicks = true,
// Pangea#
super.key,
});
@ -564,13 +566,25 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
: Colors.transparent,
max: maxPosition,
value: currentPosition,
onChanged: (position) => audioPlayer == null
? _onButtonTap()
: audioPlayer.seek(
Duration(
milliseconds: position.round(),
),
),
// #Pangea
onChanged: !widget.enableClicks
? null
: (position) => audioPlayer == null
? _onButtonTap()
: audioPlayer.seek(
Duration(
milliseconds:
position.round(),
),
),
// onChanged: (position) => audioPlayer == null
// ? _onButtonTap()
// : audioPlayer.seek(
// Duration(
// milliseconds: position.round(),
// ),
// ),
// Pangea#
),
),
],
@ -605,7 +619,7 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
child: InkWell(
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
onTap: _toggleSpeed,
onTap: !widget.enableClicks ? null : _toggleSpeed,
child: SizedBox(
width: 32,
height: 20,

View file

@ -16,6 +16,7 @@ import 'package:fluffychat/pangea/activity_sessions/activity_summary_widget.dart
import 'package:fluffychat/pangea/chat/extensions/custom_room_display_extension.dart';
import 'package:fluffychat/pangea/chat/widgets/request_regeneration_button.dart';
import 'package:fluffychat/pangea/common/widgets/pressable_button.dart';
import 'package:fluffychat/pangea/common/widgets/shimmer_background.dart';
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/utils/date_time_extension.dart';
@ -585,235 +586,241 @@ class Message extends StatelessWidget {
child: ValueListenableBuilder(
valueListenable: controller
.depressMessageButton,
child: Container(
decoration: BoxDecoration(
color: noBubble
? Colors.transparent
: color,
borderRadius:
borderRadius,
// #Pangea
child: ShimmerBackground(
enabled: controller
.showMessageShimmer(
event,
),
clipBehavior:
Clip.antiAlias,
// #Pangea
child:
CompositedTransformTarget(
link: MatrixState
.pAnyState
.layerLinkAndKey(
event.eventId,
)
.link,
// child: BubbleBackground(
// colors: colors,
// ignore: noBubble || !ownMessage,
// scrollController: scrollController,
// Pangea#
child: Container(
// #Pangea
key: MatrixState
// Pangea#
child: Container(
decoration:
BoxDecoration(
color: noBubble
? Colors
.transparent
: color,
borderRadius:
borderRadius,
),
clipBehavior:
Clip.antiAlias,
// #Pangea
child:
CompositedTransformTarget(
link: MatrixState
.pAnyState
.layerLinkAndKey(
event.eventId,
)
.key,
.link,
// child: BubbleBackground(
// colors: colors,
// ignore: noBubble || !ownMessage,
// scrollController: scrollController,
// Pangea#
decoration:
BoxDecoration(
borderRadius:
BorderRadius
.circular(
AppConfig
.borderRadius,
child: Container(
// #Pangea
key: MatrixState
.pAnyState
.layerLinkAndKey(
event.eventId,
)
.key,
// Pangea#
decoration:
BoxDecoration(
borderRadius:
BorderRadius
.circular(
AppConfig
.borderRadius,
),
),
),
constraints:
const BoxConstraints(
maxWidth: FluffyThemes
.columnWidth *
1.5,
),
child: Column(
mainAxisSize:
MainAxisSize
.min,
crossAxisAlignment:
CrossAxisAlignment
.start,
children: <Widget>[
if ({
RelationshipTypes
.reply,
RelationshipTypes
.thread,
}.contains(
event
.relationshipType,
))
FutureBuilder<
Event?>(
future: event
.getReplyEvent(
timeline,
),
builder: (
BuildContext
context,
snapshot,
) {
final replyEvent = snapshot
.hasData
? snapshot
.data!
: Event(
eventId:
event.relationshipEventId!,
content: {
'msgtype': 'm.text',
'body': '...',
},
// #Pangea
// senderId: event
// .senderId,
senderId:
"",
// Pangea#
type:
'm.room.message',
room:
event.room,
status:
EventStatus.sent,
originServerTs:
DateTime.now(),
);
return Padding(
padding:
const EdgeInsets
.only(
left:
16,
right:
16,
top: 8,
),
child:
Material(
color: Colors
.transparent,
borderRadius:
ReplyContent.borderRadius,
constraints:
const BoxConstraints(
maxWidth: FluffyThemes
.columnWidth *
1.5,
),
child: Column(
mainAxisSize:
MainAxisSize
.min,
crossAxisAlignment:
CrossAxisAlignment
.start,
children: <Widget>[
if ({
RelationshipTypes
.reply,
RelationshipTypes
.thread,
}.contains(
event
.relationshipType,
))
FutureBuilder<
Event?>(
future: event
.getReplyEvent(
timeline,
),
builder: (
BuildContext
context,
snapshot,
) {
final replyEvent = snapshot
.hasData
? snapshot
.data!
: Event(
eventId: event.relationshipEventId!,
content: {
'msgtype': 'm.text',
'body': '...',
},
// #Pangea
// senderId: event
// .senderId,
senderId: "",
// Pangea#
type: 'm.room.message',
room: event.room,
status: EventStatus.sent,
originServerTs: DateTime.now(),
);
return Padding(
padding:
const EdgeInsets.only(
left:
16,
right:
16,
top:
8,
),
child:
InkWell(
Material(
color:
Colors.transparent,
borderRadius:
ReplyContent.borderRadius,
onTap: () =>
scrollToEventId(
replyEvent.eventId,
),
child:
AbsorbPointer(
InkWell(
borderRadius:
ReplyContent.borderRadius,
onTap: () =>
scrollToEventId(
replyEvent.eventId,
),
child:
ReplyContent(
replyEvent,
ownMessage: ownMessage,
timeline: timeline,
AbsorbPointer(
child: ReplyContent(
replyEvent,
ownMessage: ownMessage,
timeline: timeline,
),
),
),
),
),
);
},
),
MessageContent(
displayEvent,
textColor:
textColor,
linkColor:
linkColor,
onInfoTab:
onInfoTab,
borderRadius:
borderRadius,
timeline:
timeline,
selected:
selected,
// #Pangea
pangeaMessageEvent:
pangeaMessageEvent,
controller:
controller,
nextEvent:
nextEvent,
prevEvent:
previousEvent,
// Pangea#
),
if (event
.hasAggregatedEvents(
timeline,
RelationshipTypes
.edit,
))
Padding(
padding:
const EdgeInsets
.only(
bottom: 8.0,
left: 16.0,
right: 16.0,
);
},
),
child: Row(
mainAxisSize:
MainAxisSize
.min,
spacing:
4.0,
children: [
Icon(
Icons
.edit_outlined,
color: textColor
.withAlpha(
164,
),
size:
14,
),
Text(
displayEvent
.originServerTs
.localizedTimeShort(
context,
),
style:
TextStyle(
MessageContent(
displayEvent,
textColor:
textColor,
linkColor:
linkColor,
onInfoTab:
onInfoTab,
borderRadius:
borderRadius,
timeline:
timeline,
selected:
selected,
// #Pangea
pangeaMessageEvent:
pangeaMessageEvent,
controller:
controller,
nextEvent:
nextEvent,
prevEvent:
previousEvent,
// Pangea#
),
if (event
.hasAggregatedEvents(
timeline,
RelationshipTypes
.edit,
))
Padding(
padding:
const EdgeInsets
.only(
bottom:
8.0,
left:
16.0,
right:
16.0,
),
child: Row(
mainAxisSize:
MainAxisSize
.min,
spacing:
4.0,
children: [
Icon(
Icons
.edit_outlined,
color:
textColor.withAlpha(
164,
),
fontSize:
11,
size:
14,
),
),
],
Text(
displayEvent
.originServerTs
.localizedTimeShort(
context,
),
style:
TextStyle(
color:
textColor.withAlpha(
164,
),
fontSize:
11,
),
),
],
),
)
// #Pangea
else if (canRefresh)
RequestRegenerationButton(
textColor:
textColor,
onPressed: () =>
controller
.requestRegeneration(
event
.eventId,
),
),
)
// #Pangea
else if (canRefresh)
RequestRegenerationButton(
textColor:
textColor,
onPressed: () =>
controller
.requestRegeneration(
event
.eventId,
),
),
// Pangea#
],
// Pangea#
],
),
),
),
),

View file

@ -208,6 +208,7 @@ class MessageContent extends StatelessWidget {
roomId: event.room.id,
senderId: event.senderId,
autoplay: overlayController != null && isTransitionAnimation,
enableClicks: overlayController != null,
// Pangea#
);
}

View file

@ -23,16 +23,19 @@ class ShimmerBackground extends StatelessWidget {
child,
if (enabled)
Positioned.fill(
child: ClipRRect(
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
child: Shimmer.fromColors(
baseColor: shimmerColor.withValues(alpha: 0.1),
highlightColor: shimmerColor.withValues(alpha: 0.6),
direction: ShimmerDirection.ltr,
child: Container(
decoration: BoxDecoration(
color: shimmerColor.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
child: IgnorePointer(
child: ClipRRect(
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
child: Shimmer.fromColors(
baseColor: shimmerColor.withValues(alpha: 0.1),
highlightColor: shimmerColor.withValues(alpha: 0.6),
direction: ShimmerDirection.ltr,
child: Container(
decoration: BoxDecoration(
color: shimmerColor.withValues(alpha: 0.3),
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
),
),
),
),

View file

@ -32,6 +32,8 @@ enum InstructionsEnum {
setLemmaEmoji,
disableLanguageTools,
selectMeaning,
clickTextMessages,
clickAudioMessages,
}
extension InstructionsEnumExtension on InstructionsEnum {
@ -63,6 +65,8 @@ extension InstructionsEnumExtension on InstructionsEnum {
case InstructionsEnum.noSavedActivitiesYet:
case InstructionsEnum.setLemmaEmoji:
case InstructionsEnum.disableLanguageTools:
case InstructionsEnum.clickTextMessages:
case InstructionsEnum.clickAudioMessages:
ErrorHandler.logError(
e: Exception("No title for this instruction"),
m: 'InstructionsEnumExtension.title',
@ -124,6 +128,8 @@ extension InstructionsEnumExtension on InstructionsEnum {
case InstructionsEnum.noSavedActivitiesYet:
return l10n.noSavedActivitiesYet;
case InstructionsEnum.setLemmaEmoji:
case InstructionsEnum.clickTextMessages:
case InstructionsEnum.clickAudioMessages:
return "";
case InstructionsEnum.disableLanguageTools:
return l10n.disableLanguageToolsDesc;