Voice Chat Fixes & Audio Player Implementation

development_mirza
Aamir Muhammad 2 years ago
parent fb3b3e8e46
commit 09c401a157

@ -552,8 +552,8 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
msg = voiceFile!.path.split("/").last;
} else {
msg = message.text;
logger.w(msg);
}
logger.w(jsonEncode(repliedMsg));
SingleUserChatModel data = SingleUserChatModel(
userChatHistoryId: 0,
chatEventId: chatEventId,
@ -1257,51 +1257,6 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
return numberStr;
}
// void playRecoding() async {
// isPlaying = true;
// await playerController.startPlayer(finishMode: FinishMode.pause);
//}
void playVoice(
BuildContext context, {
required SingleUserChatModel data,
}) async {
if (data.voice != null && data.voice!.existsSync()) {
print("Heree");
await data.voiceController!.setFilePath(data!.voice!.path);
await data.voiceController!.setLoopMode(LoopMode.off);
Duration? duration = await data.voiceController!.load();
await data.voiceController!.seek(duration);
await data.voiceController!.play();
} else {
Utils.showLoading(context);
Uint8List encodedString = await ChatApiClient().downloadURL(fileName: data.contant!, fileTypeDescription: getFileTypeDescription(data.fileTypeResponse!.fileTypeName ?? ""));
try {
String path = await downChatVoice(encodedString, data.fileTypeResponse!.fileTypeName ?? "", data);
File file = File(path!);
await file.readAsBytes();
data.voice = file;
Duration? duration = await data.voiceController!.setFilePath(file.path);
await data.voiceController!.setLoopMode(LoopMode.off);
await data.voiceController!.seek(duration);
await data.voiceController!.setVolume(1.0);
await data.voiceController!.load();
Utils.hideLoading(context);
await data.voiceController!.play();
} catch (e) {
Utils.showToast("Cannot open file.");
}
}
}
void pausePlaying(BuildContext context, {required SingleUserChatModel data}) async {
await data.voiceController!.pause();
}
void resumePlaying(BuildContext context, {required SingleUserChatModel data}) async {
await data.voiceController!.play();
}
Future<String> downChatVoice(Uint8List bytes, String ext, SingleUserChatModel data) async {
String dirPath = '${(await getApplicationDocumentsDirectory()).path}/chat_audios';
if (!await Directory(dirPath).exists()) {

@ -1,34 +1,48 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:audio_waveforms/audio_waveforms.dart' as awf;
import 'package:just_audio/just_audio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:mohem_flutter_app/api/chat/chat_api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
import 'package:mohem_flutter_app/models/chat/get_single_user_chat_list_model.dart';
import 'package:mohem_flutter_app/provider/chat_provider_model.dart';
import 'package:mohem_flutter_app/ui/chat/chat_full_image_preview.dart';
import 'package:mohem_flutter_app/ui/chat/common.dart';
import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart';
import 'package:just_audio/just_audio.dart';
class ChatBubble extends StatelessWidget {
ChatBubble({Key? key, required this.dateTime, required this.cItem}) : super(key: key);
final String dateTime;
final SingleUserChatModel cItem;
bool isCurrentUser = false;
bool isSeen = false;
bool isReplied = false;
int? fileTypeID;
String? fileTypeName;
late ChatProviderModel provider;
String? fileTypeDescription;
bool isDelivered = false;
String userName = '';
late Offset screenOffset;
void makeAssign() {
@ -42,6 +56,51 @@ class ChatBubble extends StatelessWidget {
userName = AppState().chatDetails!.response!.userName == cItem.currentUserName.toString() ? "You" : cItem.currentUserName.toString();
}
void playVoice(
BuildContext context, {
required SingleUserChatModel data,
}) async {
if (data.voice != null && data.voice!.existsSync()) {
await data.voiceController!.setFilePath(data!.voice!.path);
await data.voiceController!.setLoopMode(LoopMode.off);
Duration? duration = await data.voiceController!.load();
await data.voiceController!.seek(duration);
await data.voiceController!.play();
} else {
Utils.showLoading(context);
Uint8List encodedString = await ChatApiClient().downloadURL(fileName: data.contant!, fileTypeDescription: provider.getFileTypeDescription(data.fileTypeResponse!.fileTypeName ?? ""));
try {
String path = await provider.downChatVoice(encodedString, data.fileTypeResponse!.fileTypeName ?? "", data);
File file = File(path!);
await file.readAsBytes();
data.voice = file;
Duration? duration = await data.voiceController!.setFilePath(file.path);
await data.voiceController!.setLoopMode(LoopMode.off);
await data.voiceController!.seek(duration);
await data.voiceController!.setVolume(1.0);
await data.voiceController!.load();
Utils.hideLoading(context);
await data.voiceController!.play();
} catch (e) {
Utils.showToast("Cannot open file.");
}
}
}
void pausePlaying(BuildContext context, {required SingleUserChatModel data}) async {
await data.voiceController!.pause();
}
void rePlay(BuildContext context, {required SingleUserChatModel data}) async {
if (data.voice != null && data.voice!.existsSync()) {
await data.voiceController!.seek(Duration.zero);
await data.voiceController!.play();
}
}
Stream<PositionData> get _positionDataStream => Rx.combineLatest3<Duration, Duration, Duration?, PositionData>(cItem.voiceController!.positionStream, cItem.voiceController!.bufferedPositionStream,
cItem.voiceController!.durationStream, (Duration position, Duration bufferedPosition, Duration? duration) => PositionData(position, bufferedPosition, duration ?? Duration.zero));
@override
Widget build(BuildContext context) {
Size windowSize = MediaQuery.of(context).size;
@ -77,21 +136,18 @@ class ChatBubble extends StatelessWidget {
.paddingOnly(right: 5, top: 5, bottom: 8, left: 5),
],
).expanded,
if (cItem.userChatReplyResponse != null && cItem.userChatReplyResponse!.fileTypeId == 12 ||
cItem.userChatReplyResponse!.fileTypeId == 3 ||
cItem.userChatReplyResponse!.fileTypeId == 4)
ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: SizedBox(
height: 32,
width: 32,
child: showImage(
isReplyPreview: true,
if (cItem.userChatReplyResponse != null)
if (cItem.userChatReplyResponse!.fileTypeId == 12 || cItem.userChatReplyResponse!.fileTypeId == 3 || cItem.userChatReplyResponse!.fileTypeId == 4)
ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: SizedBox(
height: 32,
width: 32,
child: showImage(
isReplyPreview: false,
fileName: cItem.userChatReplyResponse!.contant!,
fileTypeDescription: cItem.userChatReplyResponse!.fileTypeResponse!.fileTypeDescription ?? "image/jpg")
.paddingOnly(left: 10, right: 10, bottom: 16, top: 16),
),
),
fileTypeDescription: cItem.userChatReplyResponse!.fileTypeResponse!.fileTypeDescription ?? "image/jpg")),
).paddingOnly(left: 10, right: 10, bottom: 16, top: 16),
],
),
),
@ -113,7 +169,7 @@ class ChatBubble extends StatelessWidget {
}),
),
).paddingOnly(bottom: 4),
if (fileTypeID == 13)
if (fileTypeID == 13 && cItem.voiceController != null)
currentWaveBubble(context, cItem)
else
Row(
@ -181,20 +237,19 @@ class ChatBubble extends StatelessWidget {
.paddingOnly(right: 5, top: 5, bottom: 8, left: 5),
],
).expanded,
if (cItem.userChatReplyResponse != null && cItem.userChatReplyResponse!.fileTypeId == 12 ||
cItem.userChatReplyResponse!.fileTypeId == 3 ||
cItem.userChatReplyResponse!.fileTypeId == 4)
ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: SizedBox(
height: 32,
width: 32,
child: showImage(
isReplyPreview: true,
fileName: cItem.userChatReplyResponse!.contant!,
fileTypeDescription: cItem.userChatReplyResponse!.fileTypeResponse!.fileTypeDescription ?? "image/jpg"),
),
).paddingOnly(left: 10, right: 10, bottom: 16, top: 16)
if (cItem.userChatReplyResponse != null)
if (cItem.userChatReplyResponse!.fileTypeId == 12 || cItem.userChatReplyResponse!.fileTypeId == 3 || cItem.userChatReplyResponse!.fileTypeId == 4)
ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: SizedBox(
height: 32,
width: 32,
child: showImage(
isReplyPreview: true,
fileName: cItem.userChatReplyResponse!.contant!,
fileTypeDescription: cItem.userChatReplyResponse!.fileTypeResponse!.fileTypeDescription ?? "image/jpg"),
),
).paddingOnly(left: 10, right: 10, bottom: 16, top: 16)
],
),
),
@ -216,7 +271,7 @@ class ChatBubble extends StatelessWidget {
}),
),
).paddingOnly(bottom: 4),
if (fileTypeID == 13)
if (fileTypeID == 13 && cItem.voiceController != null)
recipetWaveBubble(context, cItem)
else
Row(
@ -293,15 +348,18 @@ class ChatBubble extends StatelessWidget {
child: Row(
children: [
getPlayer(player: data.voiceController!, modelData: data),
Slider(
activeColor: Colors.white,
inactiveColor: Colors.grey,
value: 0.toDouble(),
max: 50.toDouble(),
onChanged: (double value) {
// Add code to track the music duration.
StreamBuilder<PositionData>(
stream: _positionDataStream,
builder: (BuildContext context, AsyncSnapshot<PositionData> snapshot) {
PositionData? positionData = snapshot.data;
return SeekBar(
duration: positionData?.duration ?? Duration.zero,
position: positionData?.position ?? Duration.zero,
bufferedPosition: positionData?.bufferedPosition ?? Duration.zero,
onChangeEnd: data.voiceController!.seek,
).expanded;
},
).expanded,
),
],
),
).circle(5);
@ -320,15 +378,18 @@ class ChatBubble extends StatelessWidget {
mainAxisSize: MainAxisSize.max,
children: [
getPlayer(player: data.voiceController!, modelData: data),
Slider(
activeColor: Colors.white,
inactiveColor: Colors.grey,
value: 0.toDouble(),
max: 50.toDouble(),
onChanged: (double value) {
// Add code to track the music duration.
StreamBuilder<PositionData>(
stream: _positionDataStream,
builder: (BuildContext context, AsyncSnapshot<PositionData> snapshot) {
PositionData? positionData = snapshot.data;
return SeekBar(
duration: positionData?.duration ?? Duration.zero,
position: positionData?.position ?? Duration.zero,
bufferedPosition: positionData?.bufferedPosition ?? Duration.zero,
onChangeEnd: data.voiceController!.seek,
).expanded;
},
).expanded,
),
],
),
).circle(5);
@ -354,7 +415,7 @@ class ChatBubble extends StatelessWidget {
size: 30,
color: MyColors.lightGreenColor,
).onPress(() {
provider.playVoice(context, data: modelData);
playVoice(context, data: modelData);
});
} else if (processingState != ProcessingState.completed) {
return Icon(
@ -362,7 +423,7 @@ class ChatBubble extends StatelessWidget {
size: 30,
color: MyColors.lightGreenColor,
).onPress(() {
provider.pausePlaying(context, data: modelData);
pausePlaying(context, data: modelData);
});
} else {
return Icon(
@ -370,7 +431,7 @@ class ChatBubble extends StatelessWidget {
size: 30,
color: MyColors.lightGreenColor,
).onPress(() {
player!.seek(Duration.zero);
rePlay(context, data: modelData);
});
}
},
@ -378,64 +439,4 @@ class ChatBubble extends StatelessWidget {
}
}
class WaveBubble extends StatelessWidget {
final awf.PlayerController playerController;
final VoidCallback onTap;
final bool isPlaying;
const WaveBubble({
Key? key,
required this.playerController,
required this.onTap,
required this.isPlaying,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
gradient: const LinearGradient(
transform: GradientRotation(.83),
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: <Color>[
MyColors.gradiantEndColor,
MyColors.gradiantStartColor,
],
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: onTap,
icon: Icon(isPlaying ? Icons.stop : Icons.play_arrow),
color: Colors.white,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
),
awf.AudioFileWaveforms(
size: Size(MediaQuery.of(context).size.width / 2, 10),
playerController: playerController,
padding: EdgeInsets.zero,
margin: EdgeInsets.zero,
enableSeekGesture: true,
density: 1,
playerWaveStyle: const awf.PlayerWaveStyle(
fixedWaveColor: Colors.white,
liveWaveColor: MyColors.greenColor,
showTop: true,
showBottom: true,
waveCap: StrokeCap.round,
seekLineThickness: 2,
visualizerHeight: 4,
backgroundColor: Colors.transparent,
),
),
],
),
);
}
}

@ -1,6 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'package:audio_waveforms/audio_waveforms.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -18,7 +16,7 @@ import 'package:mohem_flutter_app/models/chat/get_single_user_chat_list_model.da
import 'package:mohem_flutter_app/provider/chat_provider_model.dart';
import 'package:mohem_flutter_app/ui/chat/call/chat_outgoing_call_screen.dart';
import 'package:mohem_flutter_app/ui/chat/chat_bubble.dart';
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
import 'package:mohem_flutter_app/ui/chat/common.dart';
import 'package:mohem_flutter_app/widgets/chat_app_bar_widge.dart';
import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart';
import 'package:provider/provider.dart';

@ -81,6 +81,7 @@ class _ChatHomeState extends State<ChatHome> {
children: <Widget>[
myTab(LocaleKeys.mychats.tr(), 0),
myTab(LocaleKeys.favorite.tr(), 1),
myTab("My Team", 2),
],
),
),
@ -95,6 +96,7 @@ class _ChatHomeState extends State<ChatHome> {
children: <Widget>[
ChatHomeScreen(),
ChatFavoriteUsersScreen(),
ChatFavoriteUsersScreen(),
],
).expanded,
],

@ -0,0 +1,189 @@
import 'dart:math';
import 'package:audio_waveforms/audio_waveforms.dart';
import 'package:flutter/material.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
class SeekBar extends StatefulWidget {
final Duration duration;
final Duration position;
final Duration bufferedPosition;
final ValueChanged<Duration>? onChanged;
final ValueChanged<Duration>? onChangeEnd;
const SeekBar({
Key? key,
required this.duration,
required this.position,
required this.bufferedPosition,
this.onChanged,
this.onChangeEnd,
}) : super(key: key);
@override
SeekBarState createState() => SeekBarState();
}
class SeekBarState extends State<SeekBar> {
double? _dragValue;
late SliderThemeData _sliderThemeData;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_sliderThemeData = SliderTheme.of(context).copyWith(
// trackHeight: 2.0,
thumbColor: MyColors.lightGreenColor,
activeTrackColor: MyColors.lightGreenColor,
inactiveTrackColor: MyColors.grey57Color.withOpacity(0.4),
);
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
SliderTheme(
data: _sliderThemeData.copyWith(
thumbShape: HiddenThumbComponentShape(),
),
child: ExcludeSemantics(
child: Slider(
min: 0.0,
max: widget.duration.inMilliseconds.toDouble(),
value: min(widget.bufferedPosition.inMilliseconds.toDouble(), widget.duration.inMilliseconds.toDouble()),
onChanged: (value) {
setState(() {
_dragValue = value;
});
if (widget.onChanged != null) {
widget.onChanged!(Duration(milliseconds: value.round()));
}
},
onChangeEnd: (value) {
if (widget.onChangeEnd != null) {
widget.onChangeEnd!(Duration(milliseconds: value.round()));
}
_dragValue = null;
},
),
),
),
SliderTheme(
data: _sliderThemeData.copyWith(
inactiveTrackColor: Colors.transparent,
),
child: Slider(
min: 0.0,
max: widget.duration.inMilliseconds.toDouble(),
value: min(_dragValue ?? widget.position.inMilliseconds.toDouble(), widget.duration.inMilliseconds.toDouble()),
onChanged: (value) {
setState(() {
_dragValue = value;
});
if (widget.onChanged != null) {
widget.onChanged!(Duration(milliseconds: value.round()));
}
},
onChangeEnd: (value) {
if (widget.onChangeEnd != null) {
widget.onChangeEnd!(Duration(milliseconds: value.round()));
}
_dragValue = null;
},
),
),
],
);
}
}
class PositionData {
final Duration position;
final Duration bufferedPosition;
final Duration duration;
PositionData(this.position, this.bufferedPosition, this.duration);
}
class HiddenThumbComponentShape extends SliderComponentShape {
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) => Size.zero;
@override
void paint(
PaintingContext context,
Offset center, {
required Animation<double> activationAnimation,
required Animation<double> enableAnimation,
required bool isDiscrete,
required TextPainter labelPainter,
required RenderBox parentBox,
required SliderThemeData sliderTheme,
required TextDirection textDirection,
required double value,
required double textScaleFactor,
required Size sizeWithOverflow,
}) {}
}
class WaveBubble extends StatelessWidget {
final PlayerController playerController;
final VoidCallback onTap;
final bool isPlaying;
const WaveBubble({
Key? key,
required this.playerController,
required this.onTap,
required this.isPlaying,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
gradient: const LinearGradient(
transform: GradientRotation(.83),
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: <Color>[
MyColors.gradiantEndColor,
MyColors.gradiantStartColor,
],
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: onTap,
icon: Icon(isPlaying ? Icons.stop : Icons.play_arrow),
color: Colors.white,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
),
AudioFileWaveforms(
size: Size(MediaQuery.of(context).size.width / 2, 10),
playerController: playerController,
padding: EdgeInsets.zero,
margin: EdgeInsets.zero,
enableSeekGesture: true,
density: 1,
playerWaveStyle: const PlayerWaveStyle(
fixedWaveColor: Colors.white,
liveWaveColor: MyColors.greenColor,
showTop: true,
showBottom: true,
waveCap: StrokeCap.round,
seekLineThickness: 2,
visualizerHeight: 4,
backgroundColor: Colors.transparent,
),
),
],
),
);
}
}

@ -94,9 +94,8 @@ dependencies:
camera: ^0.10.0+4
#Chat Voice Message Recoding & Play
# record: ^4.4.3
audio_waveforms: ^0.1.5+1
# animated_text_kit: ^4.2.2
rxdart: ^0.27.7
#Encryption
flutter_des: ^2.1.0
@ -106,6 +105,7 @@ dependencies:
safe_device: ^1.1.2
flutter_layout_grid: ^2.0.1
dev_dependencies:
flutter_test:
sdk: flutter

Loading…
Cancel
Save