Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion integration_test/robots/home_robot.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import 'package:patrol/patrol.dart';
import '../base/core_robot.dart';
import 'chat_list_robot.dart';
import 'contact_list_robot.dart';
import 'setting_robot.dart';
import 'package:fluffychat/generated/l10n/app_localizations.dart';
import 'setting_robot.dart';

class HomeRobot extends CoreRobot {
HomeRobot(super.$);
Expand Down
23 changes: 12 additions & 11 deletions lib/pages/chat/chat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,8 @@ class ChatController extends State<Chat>
null;

void updateInputTextNotifier() {
if (audioRecordStateNotifier.value == AudioRecordState.recording) {
if (audioRecordStateNotifier.value == AudioRecordState.recording ||
audioRecordStateNotifier.value == AudioRecordState.paused) {
return;
}
inputText.value = sendController.text;
Expand Down Expand Up @@ -836,7 +837,7 @@ class ChatController extends State<Chat>
}

void reportEventAction(Event event) async {
if (audioRecordStateNotifier.value == AudioRecordState.recording) {
if (audioRecordStateNotifier.value != AudioRecordState.initial) {
preventActionWhileRecordingMobile(context: context);
return;
}
Expand Down Expand Up @@ -989,7 +990,7 @@ class ChatController extends State<Chat>
}

void forwardEventsAction({Event? event}) async {
if (audioRecordStateNotifier.value == AudioRecordState.recording) {
if (audioRecordStateNotifier.value != AudioRecordState.initial) {
preventActionWhileRecordingMobile(context: context);
return;
}
Expand Down Expand Up @@ -1037,7 +1038,7 @@ class ChatController extends State<Chat>
void editAction({
Event? editEvent,
}) {
if (audioRecordStateNotifier.value == AudioRecordState.recording) {
if (audioRecordStateNotifier.value != AudioRecordState.initial) {
preventActionWhileRecordingMobile(context: context);
return;
}
Expand Down Expand Up @@ -1065,7 +1066,7 @@ class ChatController extends State<Chat>
void replyAction({
Event? replyTo,
}) {
if (audioRecordStateNotifier.value == AudioRecordState.recording) {
if (audioRecordStateNotifier.value != AudioRecordState.initial) {
preventActionWhileRecordingMobile(context: context);
return;
}
Expand Down Expand Up @@ -1347,7 +1348,7 @@ class ChatController extends State<Chat>
}

void onSelectMessage(Event event) {
if (audioRecordStateNotifier.value == AudioRecordState.recording) {
if (audioRecordStateNotifier.value != AudioRecordState.initial) {
preventActionWhileRecordingMobile(context: context);
return;
}
Expand Down Expand Up @@ -1430,7 +1431,7 @@ class ChatController extends State<Chat>
static const Duration _storeInputTimeout = Duration(milliseconds: 500);

void onInputBarChanged(String text) {
if (audioRecordStateNotifier.value == AudioRecordState.recording) {
if (audioRecordStateNotifier.value != AudioRecordState.initial) {
return;
}
setReadMarker();
Expand Down Expand Up @@ -1738,7 +1739,7 @@ class ChatController extends State<Chat>
}

Future<String?> downloadFileAction(BuildContext context, Event event) async {
if (audioRecordStateNotifier.value == AudioRecordState.recording) {
if (audioRecordStateNotifier.value != AudioRecordState.initial) {
preventActionWhileRecordingMobile(context: context);
return null;
}
Expand Down Expand Up @@ -2009,7 +2010,7 @@ class ChatController extends State<Chat>
}

void onPushDetails() async {
if (audioRecordStateNotifier.value == AudioRecordState.recording) {
if (audioRecordStateNotifier.value != AudioRecordState.initial) {
preventActionWhileRecordingMobile(context: context);
return null;
}
Expand All @@ -2022,7 +2023,7 @@ class ChatController extends State<Chat>
}

void toggleSearch() {
if (audioRecordStateNotifier.value == AudioRecordState.recording) {
if (audioRecordStateNotifier.value != AudioRecordState.initial) {
preventActionWhileRecordingMobile(context: context);
return;
}
Expand Down Expand Up @@ -2334,7 +2335,7 @@ class ChatController extends State<Chat>
BuildContext context,
TapDownDetails tapDownDetails,
) async {
if (audioRecordStateNotifier.value == AudioRecordState.recording) {
if (audioRecordStateNotifier.value != AudioRecordState.initial) {
preventActionWhileRecordingMobile(context: context);
return null;
}
Expand Down
123 changes: 120 additions & 3 deletions lib/pages/chat/chat_input_row.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class ChatInputRow extends StatelessWidget {
return KeyboardVisibilityBuilder(
builder: (context, isKeyboardVisible) {
final child = Stack(
alignment: Alignment.center,
alignment: Alignment.centerRight,
children: [
Padding(
padding: _paddingInputRow(
Expand All @@ -60,7 +60,7 @@ class ChatInputRow extends StatelessWidget {
controller.audioRecordStateNotifier,
builder: (context, audioState, _) {
if (PlatformInfos.isWeb &&
audioState == AudioRecordState.recording) {
audioState != AudioRecordState.initial) {
return const SizedBox.shrink();
}

Expand Down Expand Up @@ -89,7 +89,7 @@ class ChatInputRow extends StatelessWidget {
valueListenable: controller.audioRecordStateNotifier,
builder: (context, audioState, _) {
if (PlatformInfos.isWeb &&
audioState == AudioRecordState.recording) {
audioState != AudioRecordState.initial) {
return Expanded(
child: ConstrainedBox(
constraints: const BoxConstraints(
Expand Down Expand Up @@ -145,6 +145,9 @@ class ChatInputRow extends StatelessWidget {
return ValueListenableBuilder(
valueListenable: controller.replyEventNotifier,
builder: (context, reply, _) {
final view = View.maybeOf(context);
final bottomInset = (view?.viewInsets.bottom ?? 0) /
(view?.devicePixelRatio ?? 0);
return Offstage(
offstage: text.isNotEmpty || reply != null,
child: Padding(
Expand All @@ -154,6 +157,12 @@ class ChatInputRow extends StatelessWidget {
),
child: SocialMediaRecorder(
radius: BorderRadius.circular(24),
pauseBottomPositioned:
102 + (isKeyboardVisible ? bottomInset : 16),
pauseRightPositioned: 16,
resumeDecoration: BoxDecoration(
color: LinagoraSysColors.material().surface,
),
soundRecorderWhenLockedDecoration: BoxDecoration(
borderRadius:
ChatInputRowStyle.chatInputRowBorderRadius,
Expand Down Expand Up @@ -185,6 +194,17 @@ class ChatInputRow extends StatelessWidget {
}
controller.stopRecording.call();
},
deleteRecording: () {
Logs().d('ChatInputRowMobile:: deleteRecording');
if (controller.sendController.text.isNotEmpty) {
controller.sendController.clear();
}
controller.stopRecording.call();
},
pauseRecording: () {
Logs().d('ChatInputRowMobile:: pauseRecording');
controller.pauseRecording.call();
},
sendRequestFunction: (soundFile, time, waveFrom) {
Logs().d(
'ChatInputRowMobile:: sendRequestFunction $soundFile',
Expand Down Expand Up @@ -245,6 +265,13 @@ class ChatInputRow extends StatelessWidget {
color: LinagoraSysColors.material().error,
),
),
pauseSplashColor: LinagoraSysColors.material()
.primary
.withOpacity(0.5),
pauseHighlightColor: LinagoraSysColors.material()
.primary
.withOpacity(0.2),
pauseWidget: const _AnimatedPauseButton(),
),
),
);
Expand Down Expand Up @@ -505,6 +532,96 @@ class ActionSelectModeWidget extends StatelessWidget {
}
}

class _AnimatedPauseButton extends StatefulWidget {
const _AnimatedPauseButton();

@override
State<_AnimatedPauseButton> createState() => _AnimatedPauseButtonState();
}

class _AnimatedPauseButtonState extends State<_AnimatedPauseButton>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _opacityAnimation;

@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
)..repeat(reverse: true);

_scaleAnimation = Tween<double>(
begin: 0.9,
end: 1.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
),
);

_opacityAnimation = Tween<double>(
begin: 0.5,
end: 1.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
),
);
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Opacity(
opacity: _opacityAnimation.value,
child: Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: LinagoraSysColors.material().surface,
borderRadius: BorderRadius.circular(32),
border: Border.all(
color: LinagoraSysColors.material().onPrimary,
width: 1,
),
boxShadow: [
BoxShadow(
color: LinagoraSysColors.material()
.primary
.withOpacity(0.3 * _opacityAnimation.value),
blurRadius: 8 * _opacityAnimation.value,
spreadRadius: 2 * _opacityAnimation.value,
),
],
),
child: Icon(
Icons.pause,
size: 20,
color: LinagoraRefColors.material().neutral[50],
),
),
),
);
},
);
}
}

class ChatAccountPicker extends StatelessWidget {
final ChatController controller;

Expand Down
2 changes: 1 addition & 1 deletion lib/pages/chat/chat_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ class ChatView extends StatelessWidget with MessageContentMixin {
return ValueListenableBuilder(
valueListenable: controller.audioRecordStateNotifier,
builder: (context, audioRecordState, _) {
if (audioRecordState == AudioRecordState.recording) {
if (audioRecordState != AudioRecordState.initial) {
return const SizedBox.shrink();
}
return Padding(
Expand Down
34 changes: 30 additions & 4 deletions lib/pages/chat_draft/draft_chat_input_row.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class DraftChatInputRow extends StatelessWidget {
final ValueNotifier<AudioRecordState> audioRecordStateNotifier;
final Function()? startRecording;
final Function()? stopRecording;
final Function()? pauseRecording;
final Function()? deleteRecording;
final void Function(TwakeAudioFile, Duration, List<int>)?
sendVoiceMessageAction;
final Function()? onTapRecorderWeb;
Expand Down Expand Up @@ -63,6 +65,8 @@ class DraftChatInputRow extends StatelessWidget {
this.onFinishRecorderWeb,
this.onDeleteRecorderWeb,
required this.recordDurationWebNotifier,
this.pauseRecording,
this.deleteRecording,
});

@override
Expand All @@ -89,7 +93,7 @@ class DraftChatInputRow extends StatelessWidget {
valueListenable: audioRecordStateNotifier,
builder: (context, audioState, _) {
if (PlatformInfos.isWeb &&
audioState == AudioRecordState.recording) {
audioState != AudioRecordState.initial) {
return const SizedBox.shrink();
}
return SizedBox(
Expand All @@ -107,7 +111,7 @@ class DraftChatInputRow extends StatelessWidget {
valueListenable: audioRecordStateNotifier,
builder: (context, audioState, _) {
if (PlatformInfos.isWeb &&
audioState == AudioRecordState.recording) {
audioState != AudioRecordState.initial) {
return Expanded(
child: ConstrainedBox(
constraints: const BoxConstraints(
Expand Down Expand Up @@ -169,6 +173,9 @@ class DraftChatInputRow extends StatelessWidget {
return ValueListenableBuilder(
valueListenable: inputText,
builder: (context, text, _) {
final view = View.maybeOf(context);
final bottomInset =
(view?.viewInsets.bottom ?? 0) / (view?.devicePixelRatio ?? 0);
return Offstage(
offstage: text.isNotEmpty,
child: Padding(
Expand All @@ -178,6 +185,12 @@ class DraftChatInputRow extends StatelessWidget {
),
child: SocialMediaRecorder(
radius: BorderRadius.circular(24),
pauseBottomPositioned:
102 + (isKeyboardVisible ? bottomInset : 16),
pauseRightPositioned: 16,
resumeDecoration: BoxDecoration(
color: LinagoraSysColors.material().surface,
),
soundRecorderWhenLockedDecoration: BoxDecoration(
borderRadius: ChatInputRowStyle.chatInputRowBorderRadius,
color: LinagoraSysColors.material().onPrimary,
Expand All @@ -196,16 +209,29 @@ class DraftChatInputRow extends StatelessWidget {
),
microphoneRequestPermission: onLongPressAudioRecord,
startRecording: () {
Logs().d('ChatInputRowMobile:: startRecording');
Logs().d('DraftChatInputRow:: startRecording');
startRecording?.call();
},
stopRecording: (_) {
Logs().d('ChatInputRowMobile:: stopRecording');
if (audioRecordStateNotifier.value !=
AudioRecordState.recording) {
return;
}
Logs().d('DraftChatInputRow:: stopRecording');
stopRecording?.call();
},
pauseRecording: () {
Logs().d('DraftChatInputRow:: pauseRecording');
pauseRecording?.call();
},
deleteRecording: () {
Logs().d('DraftChatInputRow:: deleteRecording');
deleteRecording?.call();
},
sendRequestFunction: (soundFile, time, waveFrom) {
Logs().d(
'ChatInputRowMobile:: sendRequestFunction $soundFile',
'DraftChatInputRow:: sendRequestFunction $soundFile',
);
stopRecording?.call();

Expand Down
Loading
Loading