Skip to content

Commit

Permalink
Merge pull request #2697 from terry-brett/master
Browse files Browse the repository at this point in the history
Voice Recorder iOS 17 style
  • Loading branch information
zlshames authored Oct 4, 2024
2 parents 717f219 + 59db29c commit f273b3d
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:bluebubbles/app/layouts/conversation_view/widgets/message/send_a
import 'package:bluebubbles/app/layouts/conversation_view/widgets/text_field/picked_attachments_holder.dart';
import 'package:bluebubbles/app/layouts/conversation_view/widgets/text_field/reply_holder.dart';
import 'package:bluebubbles/app/layouts/conversation_view/widgets/text_field/text_field_suffix.dart';
import 'package:bluebubbles/app/layouts/conversation_view/widgets/text_field/voice_message_recorder.dart';
import 'package:bluebubbles/app/wrappers/stateful_boilerplate.dart';
import 'package:bluebubbles/helpers/helpers.dart';
import 'package:bluebubbles/database/models.dart';
Expand All @@ -20,8 +21,8 @@ import 'package:chunked_stream/chunked_stream.dart';
import 'package:collection/collection.dart';
import 'package:emojis/emoji.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart' hide Emoji;
import 'package:file_picker/file_picker.dart' hide PlatformFile;
import 'package:file_picker/file_picker.dart' as pf;
import 'package:file_picker/file_picker.dart' hide PlatformFile;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -586,7 +587,7 @@ class ConversationTextFieldState extends CustomState<ConversationTextField, void
IconButton(
icon: Icon(
iOS
? CupertinoIcons.square_arrow_up_on_square_fill
? CupertinoIcons.add_circled_solid
: material
? Icons.add_circle_outline
: Icons.add,
Expand Down Expand Up @@ -759,27 +760,12 @@ class ConversationTextFieldState extends CustomState<ConversationTextField, void
),
);
})
: AudioWaveforms(
size: Size(textFieldSize.width - (samsung ? 0 : 80), textFieldSize.height - 15),
recorderController: recorderController!,
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
waveStyle: const WaveStyle(
waveColor: Colors.white,
waveCap: StrokeCap.square,
spacing: 4.0,
showBottom: true,
extendWaveform: true,
showMiddleLine: false,
),
decoration: BoxDecoration(
border: Border.fromBorderSide(BorderSide(
color: context.theme.colorScheme.outline,
width: 1,
)),
borderRadius: BorderRadius.circular(20),
color: context.theme.colorScheme.properSurface,
),
);
: VoiceMessageRecorder(
recorderController: recorderController,
textFieldSize: textFieldSize,
iOS: iOS,
samsung: samsung,
);
}),
))),
SendAnimation(parentController: controller),
Expand Down Expand Up @@ -903,7 +889,7 @@ class ConversationTextFieldState extends CustomState<ConversationTextField, void
}
}

class TextFieldComponent extends StatelessWidget {
class TextFieldComponent extends StatefulWidget {
const TextFieldComponent({
super.key,
required this.subjectTextController,
Expand All @@ -924,6 +910,47 @@ class TextFieldComponent extends StatelessWidget {

final List<PlatformFile> initialAttachments;

@override
State<StatefulWidget> createState() => TextFieldComponentState();
}


class TextFieldComponentState extends State<TextFieldComponent> {
late final ConversationViewController? controller;
late final FocusNode? focusNode;
late final RecorderController? recorderController;
late final List<PlatformFile> initialAttachments;
late final MentionTextEditingController textController;
late final SpellCheckTextEditingController subjectTextController;
late final sendMessage;

late final ValueNotifier<bool> isRecordingNotifier;
TextFieldComponentState() : isRecordingNotifier = ValueNotifier<bool>(false);

@override
void initState() {
super.initState();
controller = widget.controller;
focusNode = widget.focusNode;
recorderController = widget.recorderController;
initialAttachments = widget.initialAttachments;
textController = widget.textController;
subjectTextController = widget.subjectTextController;
sendMessage = widget.sendMessage;

// add a listener to recorderController to update isRecordingNotifier
recorderController?.addListener(() {
isRecordingNotifier.value = recorderController?.isRecording ?? false;
});
}

@override
void dispose() {
// dispose of the ValueNotifier when the state is disposed
isRecordingNotifier.dispose();
super.dispose();
}

bool get iOS => ss.settings.skin.value == Skins.iOS;

bool get samsung => ss.settings.skin.value == Skins.Samsung;
Expand All @@ -938,11 +965,14 @@ class TextFieldComponent extends StatelessWidget {
onKeyEvent: (_, ev) => handleKey(_, ev, context, isChatCreator),
child: Padding(
padding: const EdgeInsets.only(right: 5.0),
child: Container(
child: ValueListenableBuilder<bool>(
valueListenable: isRecordingNotifier,
builder: (context, isRecording, child) {
return Container(
decoration: iOS
? BoxDecoration(
border: Border.fromBorderSide(BorderSide(
color: context.theme.colorScheme.properSurface,
color: (isRecording & iOS) ? context.theme.colorScheme.primary.withOpacity(1.0) : context.theme.colorScheme.properSurface,
width: 1.5,
)),
borderRadius: BorderRadius.circular(20),
Expand Down Expand Up @@ -1046,19 +1076,21 @@ class TextFieldComponent extends StatelessWidget {
? "New Message"
: ss.settings.recipientAsPlaceholder.value == true
? chat!.getTitle()
: chat!.isTextForwarding
: (chat!.isTextForwarding && !isRecording)
? "Text Forwarding"
: "iMessage",
: (!isRecording) // Only show iMessage when not recording
? "iMessage" : "",
enabledBorder: InputBorder.none,
border: InputBorder.none,
focusedBorder: InputBorder.none,
fillColor: Colors.transparent,
filled: (isRecording & iOS),
fillColor: (isRecording & iOS) ? context.theme.colorScheme.primary.withOpacity(0.3) : Colors.transparent,
hintStyle: context.theme.extension<BubbleText>()!.bubbleText.copyWith(color: context.theme.colorScheme.outline),
suffixIconConstraints: const BoxConstraints(minHeight: 0),
suffixIcon: samsung && !isChatCreator
? null
: Padding(
padding: EdgeInsets.only(right: iOS ? 0.0 : 5.0),
padding: EdgeInsets.only(right: 5.0),
child: TextFieldSuffix(
subjectTextController: controller?.subjectTextController ?? subjectTextController,
textController: controller?.textController ?? textController,
Expand Down Expand Up @@ -1151,6 +1183,8 @@ class TextFieldComponent extends StatelessWidget {
],
),
),
);
}
),
),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@

import 'package:audio_waveforms/audio_waveforms.dart';
import 'package:bluebubbles/app/components/custom_text_editing_controllers.dart';
import 'package:bluebubbles/app/layouts/conversation_view/widgets/effects/send_effect_picker.dart';
import 'package:bluebubbles/app/layouts/conversation_view/widgets/message/attachment/audio_player.dart';
import 'package:bluebubbles/app/layouts/conversation_view/widgets/text_field/send_button.dart';
import 'package:bluebubbles/app/layouts/conversation_view/widgets/effects/send_effect_picker.dart';
import 'package:bluebubbles/app/wrappers/cupertino_icon_wrapper.dart';
import 'package:bluebubbles/app/wrappers/stateful_boilerplate.dart';
import 'package:bluebubbles/helpers/helpers.dart';
Expand Down Expand Up @@ -73,7 +73,7 @@ class _TextFieldSuffixState extends OptimizedState<TextFieldSuffix> {
? null
: !isChatCreator && !showRecording
? context.theme.colorScheme.outline
: context.theme.colorScheme.primary,
: context.theme.colorScheme.primary.withOpacity(0.4),
shape: const CircleBorder(),
padding: const EdgeInsets.all(0),
maximumSize: kIsDesktop ? const Size(40, 40) : const Size(32, 32),
Expand All @@ -83,12 +83,12 @@ class _TextFieldSuffixState extends OptimizedState<TextFieldSuffix> {
child: isLinuxArm64 ? const SizedBox(height: 40) :
!isChatCreator && !showRecording
? CupertinoIconWrapper(icon: Icon(
iOS ? CupertinoIcons.mic : Icons.mic_none,
iOS ? CupertinoIcons.waveform : Icons.mic_none,
color: iOS ? context.theme.colorScheme.outline : context.theme.colorScheme.properOnSurface,
size: 20,
size: iOS ? 24 : 20, // Waveform icon appears smaller, using size 24
)) : CupertinoIconWrapper(icon: Icon(
iOS ? CupertinoIcons.stop_fill : Icons.stop_circle,
color: iOS ? context.theme.colorScheme.onPrimary : context.theme.colorScheme.properOnSurface,
color: iOS ? context.theme.colorScheme.primary : context.theme.colorScheme.properOnSurface,
size: 15,
)),
onPressed: () async {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import 'dart:async';

import 'package:audio_waveforms/audio_waveforms.dart';
import 'package:bluebubbles/helpers/helpers.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';


class VoiceMessageRecorder extends StatefulWidget {
const VoiceMessageRecorder({
super.key,
required this.recorderController,
required this.textFieldSize,
required this.iOS,
required this.samsung,
});

final RecorderController? recorderController;

final Size textFieldSize;

final bool iOS;

final bool samsung;

@override
_VoiceMessageRecorderState createState() => _VoiceMessageRecorderState();
}

class _VoiceMessageRecorderState extends State<VoiceMessageRecorder> {
late Stream<Duration> recordingDurationStream;

@override
void initState() {
recordingDurationStream = widget.recorderController!.onCurrentDuration;

super.initState();
}

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 1),
child: Row(
children: [
AudioWaveforms(
size: Size(
widget.textFieldSize.width - getWidth(widget.iOS, widget.samsung),
widget.textFieldSize.height - 15),
recorderController: widget.recorderController!,
padding: EdgeInsets.symmetric(vertical: 5, horizontal: widget.iOS ? 10 : 15), // need extra spacing in case of iOS for recording duration
waveStyle: WaveStyle(
waveColor: widget.iOS ? context.theme.colorScheme.primary : context.theme.colorScheme.properOnSurface,
waveCap: StrokeCap.square,
spacing: 4.0,
showBottom: true,
extendWaveform: true,
showMiddleLine: false,
),
decoration: BoxDecoration(
border: Border.fromBorderSide(BorderSide(
color: widget.iOS
? Colors.transparent
: context.theme.colorScheme.outline,
width: 1,
)),
borderRadius: BorderRadius.circular(20),
color: widget.iOS
? Colors.transparent
: context.theme.colorScheme.properSurface,
),
),
Visibility(
visible: widget.iOS,
child: Center(
child: StreamBuilder<Duration>(
stream: recordingDurationStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
final minutes = snapshot.data!.inMinutes;

final seconds = (snapshot.data!.inSeconds % 60)
.toString()
.padLeft(2, '0');

return Text(
'$minutes:$seconds',
style: TextStyle(color: context.theme.colorScheme.primary),
);
} else {
return Container();
}
},
),
),
),
],
));
}

int getWidth(bool iOS, bool samsung){
if (samsung){
// width for samsung style
return 0;
} else if (iOS){
return 105;
}
// for material
return 80;
}
}

0 comments on commit f273b3d

Please sign in to comment.