D1APP-101 chat waveform changed to slider
This commit is contained in:
parent
abe153d1ff
commit
1c62f956c7
|
|
@ -4,10 +4,10 @@ class ChatRoom {
|
||||||
final int id;
|
final int id;
|
||||||
final String type;
|
final String type;
|
||||||
final String updatedAt;
|
final String updatedAt;
|
||||||
final int unread;
|
int unread;
|
||||||
final LastMessage lastMessage;
|
final LastMessage lastMessage;
|
||||||
|
|
||||||
const ChatRoom({
|
ChatRoom({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:just_waveform/just_waveform.dart';
|
|
||||||
|
|
||||||
import 'radar_attachment.dart';
|
import 'radar_attachment.dart';
|
||||||
|
|
||||||
|
|
@ -15,7 +12,6 @@ class MessageData {
|
||||||
final String createdAt;
|
final String createdAt;
|
||||||
final RadarAttachment? radar;
|
final RadarAttachment? radar;
|
||||||
final File? audioFile;
|
final File? audioFile;
|
||||||
final Waveform? waveform;
|
|
||||||
final int? audioDuration;
|
final int? audioDuration;
|
||||||
|
|
||||||
const MessageData({
|
const MessageData({
|
||||||
|
|
@ -26,7 +22,6 @@ class MessageData {
|
||||||
this.text,
|
this.text,
|
||||||
this.audio,
|
this.audio,
|
||||||
this.radar,
|
this.radar,
|
||||||
this.waveform,
|
|
||||||
this.audioFile,
|
this.audioFile,
|
||||||
this.audioDuration,
|
this.audioDuration,
|
||||||
});
|
});
|
||||||
|
|
@ -41,20 +36,6 @@ class MessageData {
|
||||||
audioDuration: json['waveform'] == null
|
audioDuration: json['waveform'] == null
|
||||||
? null
|
? null
|
||||||
: jsonDecode(json['waveform'])['duration'] ?? 0,
|
: jsonDecode(json['waveform'])['duration'] ?? 0,
|
||||||
waveform: json['waveform'] != null
|
|
||||||
? Waveform(
|
|
||||||
version: 1,
|
|
||||||
flags: 0,
|
|
||||||
sampleRate: 44100,
|
|
||||||
samplesPerPixel: 441,
|
|
||||||
length: jsonDecode(json['waveform'])['length'],
|
|
||||||
data: Int16List.fromList(
|
|
||||||
List<int>.from(
|
|
||||||
jsonDecode(json['waveform'])['data'],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
radar: json['radar'] == null
|
radar: json['radar'] == null
|
||||||
? null
|
? null
|
||||||
: RadarAttachment.fromJson(json['radar']),
|
: RadarAttachment.fromJson(json['radar']),
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ class _DirectState extends State<Direct> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final state = context.read<DirectState>();
|
final state = context.read<DirectState>();
|
||||||
|
state.replyRadar = widget.pageData['radarAttachment'];
|
||||||
final typeId = ServerDataProvider.labelToTypeId(widget.pageData['type']);
|
final typeId = ServerDataProvider.labelToTypeId(widget.pageData['type']);
|
||||||
state.typeId = typeId;
|
state.typeId = typeId;
|
||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
|
|
@ -48,18 +49,23 @@ class _DirectState extends State<Direct> {
|
||||||
appBarData: AppBarData(
|
appBarData: AppBarData(
|
||||||
hasBack: true,
|
hasBack: true,
|
||||||
subtitle: 'ارتباط با سردبیر',
|
subtitle: 'ارتباط با سردبیر',
|
||||||
title: widget.pageData['type'].substring(7),
|
title: widget.pageData['type'] ?? 'پشتیبانی اپلیکیشن',
|
||||||
),
|
),
|
||||||
slivers: [
|
slivers: [
|
||||||
if (state.appState != AppState.busy)
|
if (state.appState != AppState.busy)
|
||||||
SliverStateHandler<DirectState>(
|
SliverPadding(
|
||||||
itemPadding: const EdgeInsets.only(bottom: 12),
|
padding: state.replyRadar == null
|
||||||
state: state,
|
? EdgeInsets.zero
|
||||||
builder: (context, state, index) => Message(
|
: const EdgeInsets.only(bottom: 68),
|
||||||
message: state.messages[index],
|
sliver: SliverStateHandler<DirectState>(
|
||||||
|
itemPadding: const EdgeInsets.only(bottom: 12),
|
||||||
|
state: state,
|
||||||
|
builder: (context, state, index) => Message(
|
||||||
|
message: state.messages[index],
|
||||||
|
),
|
||||||
|
childCount: state.messages.length,
|
||||||
|
onRetry: state.getMessages,
|
||||||
),
|
),
|
||||||
childCount: state.messages.length,
|
|
||||||
onRetry: state.getMessages,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
children: [
|
children: [
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:didvan/models/enums.dart';
|
import 'package:didvan/models/enums.dart';
|
||||||
|
|
@ -9,7 +8,6 @@ import 'package:didvan/services/network/request.dart';
|
||||||
import 'package:didvan/services/network/request_helper.dart';
|
import 'package:didvan/services/network/request_helper.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_vibrate/flutter_vibrate.dart';
|
import 'package:flutter_vibrate/flutter_vibrate.dart';
|
||||||
import 'package:just_waveform/just_waveform.dart';
|
|
||||||
import 'package:record/record.dart';
|
import 'package:record/record.dart';
|
||||||
|
|
||||||
class DirectState extends CoreProvier {
|
class DirectState extends CoreProvier {
|
||||||
|
|
@ -21,7 +19,7 @@ class DirectState extends CoreProvier {
|
||||||
String? text;
|
String? text;
|
||||||
RadarAttachment? replyRadar;
|
RadarAttachment? replyRadar;
|
||||||
File? recordedFile;
|
File? recordedFile;
|
||||||
Waveform? waveform;
|
int? audioDuration;
|
||||||
|
|
||||||
bool isRecording = false;
|
bool isRecording = false;
|
||||||
|
|
||||||
|
|
@ -90,9 +88,7 @@ class DirectState extends CoreProvier {
|
||||||
|
|
||||||
Future<void> sendMessage() async {
|
Future<void> sendMessage() async {
|
||||||
if ((text == null || text!.isEmpty) && recordedFile == null) return;
|
if ((text == null || text!.isEmpty) && recordedFile == null) return;
|
||||||
if (recordedFile != null) {
|
replyRadar = null;
|
||||||
while (waveform == null) {}
|
|
||||||
}
|
|
||||||
messages.insert(
|
messages.insert(
|
||||||
0,
|
0,
|
||||||
MessageData(
|
MessageData(
|
||||||
|
|
@ -105,8 +101,7 @@ class DirectState extends CoreProvier {
|
||||||
audio: null,
|
audio: null,
|
||||||
audioFile: recordedFile,
|
audioFile: recordedFile,
|
||||||
radar: replyRadar,
|
radar: replyRadar,
|
||||||
waveform: waveform,
|
audioDuration: audioDuration,
|
||||||
audioDuration: waveform != null ? waveform!.duration.inSeconds : null,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_addToDailyGrouped();
|
_addToDailyGrouped();
|
||||||
|
|
@ -126,13 +121,6 @@ class DirectState extends CoreProvier {
|
||||||
if (uploadFile == null) {
|
if (uploadFile == null) {
|
||||||
service.post();
|
service.post();
|
||||||
} else {
|
} else {
|
||||||
body.addAll({
|
|
||||||
'waveform': jsonEncode({
|
|
||||||
'data': waveform!.data,
|
|
||||||
'length': waveform!.length,
|
|
||||||
'duration': waveform!.duration,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
service.multipart(
|
service.multipart(
|
||||||
file: uploadFile,
|
file: uploadFile,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:didvan/models/message_data/message_data.dart';
|
||||||
|
import 'package:didvan/pages/home/widgets/audio_slider.dart';
|
||||||
|
import 'package:didvan/pages/home/widgets/player_controller_button.dart';
|
||||||
|
import 'package:didvan/services/media/media.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AudioWidget extends StatelessWidget {
|
||||||
|
final String? audioUrl;
|
||||||
|
final File? audioFile;
|
||||||
|
const AudioWidget({Key? key, this.audioUrl, this.audioFile})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StreamBuilder<bool>(
|
||||||
|
stream: MediaService.audioPlayer.playingStream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: AudioSlider(
|
||||||
|
tag: audioUrl ?? audioFile!.path,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AudioControllerButton(
|
||||||
|
audioFile: audioFile,
|
||||||
|
audioUrl: audioUrl,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
import 'package:didvan/config/design_config.dart';
|
import 'package:didvan/config/design_config.dart';
|
||||||
import 'package:didvan/config/theme_data.dart';
|
import 'package:didvan/config/theme_data.dart';
|
||||||
|
import 'package:didvan/constants/app_icons.dart';
|
||||||
import 'package:didvan/models/message_data/message_data.dart';
|
import 'package:didvan/models/message_data/message_data.dart';
|
||||||
import 'package:didvan/pages/home/direct/direct_state.dart';
|
import 'package:didvan/pages/home/direct/direct_state.dart';
|
||||||
import 'package:didvan/pages/home/widgets/audio_visualizer.dart';
|
import 'package:didvan/pages/home/direct/widgets/audio_widget.dart';
|
||||||
|
import 'package:didvan/pages/home/widgets/audio_slider.dart';
|
||||||
|
import 'package:didvan/pages/home/widgets/player_controller_button.dart';
|
||||||
|
import 'package:didvan/services/media/media.dart';
|
||||||
import 'package:didvan/utils/date_time.dart';
|
import 'package:didvan/utils/date_time.dart';
|
||||||
import 'package:didvan/widgets/didvan/divider.dart';
|
import 'package:didvan/widgets/didvan/divider.dart';
|
||||||
import 'package:didvan/widgets/didvan/text.dart';
|
import 'package:didvan/widgets/didvan/text.dart';
|
||||||
|
|
@ -60,13 +64,9 @@ class Message extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
if (message.text != null) DidvanText(message.text!),
|
if (message.text != null) DidvanText(message.text!),
|
||||||
if (message.audio != null || message.audioFile != null)
|
if (message.audio != null || message.audioFile != null)
|
||||||
SizedBox(
|
AudioWidget(
|
||||||
height: 50,
|
audioFile: message.audioFile,
|
||||||
child: AudioVisualizer(
|
audioUrl: message.audio,
|
||||||
audioUrl: message.audio,
|
|
||||||
waveform: message.waveform,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (message.radar != null) const DidvanDivider(),
|
if (message.radar != null) const DidvanDivider(),
|
||||||
if (message.radar != null) const SizedBox(height: 4),
|
if (message.radar != null) const SizedBox(height: 4),
|
||||||
|
|
@ -77,10 +77,22 @@ class Message extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
DidvanText(
|
Row(
|
||||||
DateTimeUtils.timeWithAmPm(message.createdAt),
|
mainAxisSize: MainAxisSize.min,
|
||||||
style: Theme.of(context).textTheme.overline,
|
children: [
|
||||||
color: Theme.of(context).colorScheme.caption,
|
DidvanText(
|
||||||
|
DateTimeUtils.timeWithAmPm(message.createdAt),
|
||||||
|
style: Theme.of(context).textTheme.overline,
|
||||||
|
color: Theme.of(context).colorScheme.caption,
|
||||||
|
),
|
||||||
|
if (!message.writedByAdmin)
|
||||||
|
Icon(
|
||||||
|
message.readed
|
||||||
|
? DidvanIcons.check_double_light
|
||||||
|
: DidvanIcons.check_light,
|
||||||
|
size: 16,
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import 'package:didvan/config/design_config.dart';
|
||||||
import 'package:didvan/config/theme_data.dart';
|
import 'package:didvan/config/theme_data.dart';
|
||||||
import 'package:didvan/constants/app_icons.dart';
|
import 'package:didvan/constants/app_icons.dart';
|
||||||
import 'package:didvan/pages/home/direct/direct_state.dart';
|
import 'package:didvan/pages/home/direct/direct_state.dart';
|
||||||
import 'package:didvan/pages/home/widgets/audio_visualizer.dart';
|
import 'package:didvan/pages/home/direct/widgets/audio_widget.dart';
|
||||||
import 'package:didvan/widgets/didvan/icon_button.dart';
|
import 'package:didvan/widgets/didvan/icon_button.dart';
|
||||||
import 'package:didvan/widgets/didvan/text.dart';
|
import 'package:didvan/widgets/didvan/text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -11,10 +11,72 @@ import 'package:provider/provider.dart';
|
||||||
class MessageBox extends StatelessWidget {
|
class MessageBox extends StatelessWidget {
|
||||||
const MessageBox({Key? key}) : super(key: key);
|
const MessageBox({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Consumer<DirectState>(
|
||||||
|
builder: (context, state, child) => state.replyRadar != null
|
||||||
|
? _MessageBoxContainer(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const DidvanText(
|
||||||
|
'لینک به مطلب:',
|
||||||
|
),
|
||||||
|
DidvanText(
|
||||||
|
state.replyRadar!.title,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DidvanIconButton(
|
||||||
|
icon: DidvanIcons.close_regular,
|
||||||
|
gestureSize: 24,
|
||||||
|
onPressed: () {
|
||||||
|
state.replyRadar = null;
|
||||||
|
state.update();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
|
),
|
||||||
|
_MessageBoxContainer(
|
||||||
|
child: Consumer<DirectState>(
|
||||||
|
builder: (context, state, child) {
|
||||||
|
if (state.isRecording) {
|
||||||
|
return const _Recording();
|
||||||
|
} else if (!state.isRecording && state.recordedFile != null) {
|
||||||
|
return const _RecordChecking();
|
||||||
|
}
|
||||||
|
return const _Typing();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MessageBoxContainer extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
const _MessageBoxContainer({Key? key, required this.child}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 56,
|
height: 68,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
top: BorderSide(
|
top: BorderSide(
|
||||||
|
|
@ -23,16 +85,7 @@ class MessageBox extends StatelessWidget {
|
||||||
),
|
),
|
||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
),
|
),
|
||||||
child: Consumer<DirectState>(
|
child: child,
|
||||||
builder: (context, state, child) {
|
|
||||||
if (state.isRecording) {
|
|
||||||
return const _Recording();
|
|
||||||
} else if (!state.isRecording && state.recordedFile != null) {
|
|
||||||
return const _RecordChecking();
|
|
||||||
}
|
|
||||||
return const _Typing();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -153,9 +206,7 @@ class _RecordChecking extends StatelessWidget {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
child: AudioVisualizer(
|
child: AudioWidget(audioFile: state.recordedFile!),
|
||||||
audioFile: state.recordedFile!,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
DidvanIconButton(
|
DidvanIconButton(
|
||||||
|
|
|
||||||
|
|
@ -64,10 +64,7 @@ class _NewsDetailsState extends State<NewsDetails> {
|
||||||
hasUnmarkConfirmation:
|
hasUnmarkConfirmation:
|
||||||
widget.pageData['hasUnmarkConfirmation'],
|
widget.pageData['hasUnmarkConfirmation'],
|
||||||
scrollController: _scrollController,
|
scrollController: _scrollController,
|
||||||
comments: state.currentNews.comments,
|
item: state.currentNews,
|
||||||
id: state.currentNews.id,
|
|
||||||
marked: state.currentNews.marked,
|
|
||||||
title: state.currentNews.title,
|
|
||||||
onCommentsChanged: state.onCommentsChanged,
|
onCommentsChanged: state.onCommentsChanged,
|
||||||
onMarkChanged: (value) => widget.pageData['onMarkChanged'](
|
onMarkChanged: (value) => widget.pageData['onMarkChanged'](
|
||||||
state.currentNews.id,
|
state.currentNews.id,
|
||||||
|
|
|
||||||
|
|
@ -63,18 +63,14 @@ class _RadarDetailsState extends State<RadarDetails> {
|
||||||
child: FloatingNavigationBar(
|
child: FloatingNavigationBar(
|
||||||
hasUnmarkConfirmation:
|
hasUnmarkConfirmation:
|
||||||
widget.pageData['hasUnmarkConfirmation'],
|
widget.pageData['hasUnmarkConfirmation'],
|
||||||
comments: state.currentRadar.comments,
|
|
||||||
id: state.currentRadar.id,
|
|
||||||
isRadar: true,
|
isRadar: true,
|
||||||
marked: state.currentRadar.marked,
|
|
||||||
title: state.currentRadar.title,
|
|
||||||
categories: state.currentRadar.categories,
|
|
||||||
scrollController: _scrollController,
|
scrollController: _scrollController,
|
||||||
onMarkChanged: (value) =>
|
onMarkChanged: (value) =>
|
||||||
widget.pageData['onMarkChanged']?.call(
|
widget.pageData['onMarkChanged']?.call(
|
||||||
state.currentRadar.id,
|
state.currentRadar.id,
|
||||||
value,
|
value,
|
||||||
),
|
),
|
||||||
|
item: state.currentRadar,
|
||||||
onCommentsChanged: (count) {
|
onCommentsChanged: (count) {
|
||||||
state.onCommentsChanged(count);
|
state.onCommentsChanged(count);
|
||||||
widget.pageData['onCommentsChanged']?.call(
|
widget.pageData['onCommentsChanged']?.call(
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:didvan/constants/assets.dart';
|
import 'package:didvan/constants/assets.dart';
|
||||||
import 'package:didvan/models/view/app_bar_data.dart';
|
import 'package:didvan/models/view/app_bar_data.dart';
|
||||||
import 'package:didvan/pages/home/settings/direct_list/direct_list_state.dart';
|
import 'package:didvan/pages/home/settings/direct_list/direct_list_state.dart';
|
||||||
import 'package:didvan/pages/home/settings/direct_list/widgets/chat_room_item.dart';
|
import 'package:didvan/pages/home/settings/direct_list/widgets/direct_item.dart';
|
||||||
import 'package:didvan/widgets/didvan/badge.dart';
|
import 'package:didvan/widgets/didvan/badge.dart';
|
||||||
import 'package:didvan/widgets/didvan/divider.dart';
|
import 'package:didvan/widgets/didvan/divider.dart';
|
||||||
import 'package:didvan/widgets/didvan/scaffold.dart';
|
import 'package:didvan/widgets/didvan/scaffold.dart';
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,13 @@ class ChatRoomItem extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => Navigator.of(context).pushNamed(
|
onTap: () {
|
||||||
Routes.direct,
|
Navigator.of(context).pushNamed(
|
||||||
arguments: {'type': chatRoom.type},
|
Routes.direct,
|
||||||
),
|
arguments: {'type': chatRoom.type},
|
||||||
|
);
|
||||||
|
chatRoom.unread = 0;
|
||||||
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -62,10 +65,12 @@ class ChatRoomItem extends StatelessWidget {
|
||||||
DidvanIcons.mic_light,
|
DidvanIcons.mic_light,
|
||||||
size: 18,
|
size: 18,
|
||||||
),
|
),
|
||||||
DidvanText(
|
Expanded(
|
||||||
chatRoom.lastMessage.text ?? 'پیام صوتی',
|
child: DidvanText(
|
||||||
maxLines: 1,
|
chatRoom.lastMessage.text ?? 'پیام صوتی',
|
||||||
overflow: TextOverflow.ellipsis,
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
|
||||||
|
import 'package:didvan/services/media/media.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AudioSlider extends StatelessWidget {
|
||||||
|
final String tag;
|
||||||
|
const AudioSlider({Key? key, required this.tag}) : super(key: key);
|
||||||
|
|
||||||
|
bool get _isPlaying => MediaService.audioPlayerTag == tag;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IgnorePointer(
|
||||||
|
ignoring: MediaService.audioPlayerTag != tag,
|
||||||
|
child: Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: StreamBuilder<Duration>(
|
||||||
|
stream: _isPlaying ? MediaService.audioPlayer.positionStream : null,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return ProgressBar(
|
||||||
|
total: MediaService.audioPlayer.duration ?? Duration.zero,
|
||||||
|
progress: snapshot.data ?? Duration.zero,
|
||||||
|
buffered:
|
||||||
|
_isPlaying ? MediaService.audioPlayer.bufferedPosition : null,
|
||||||
|
thumbRadius: 6,
|
||||||
|
barHeight: 3,
|
||||||
|
timeLabelTextStyle: const TextStyle(fontSize: 0),
|
||||||
|
onSeek: (value) => _onSeek(value.inMilliseconds),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSeek(int value) {
|
||||||
|
MediaService.audioPlayer.seek(Duration(milliseconds: value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,314 +1,314 @@
|
||||||
import 'dart:io';
|
// import 'dart:io';
|
||||||
import 'dart:math';
|
// import 'dart:math';
|
||||||
|
|
||||||
import 'package:didvan/config/design_config.dart';
|
// import 'package:didvan/config/design_config.dart';
|
||||||
import 'package:didvan/config/theme_data.dart';
|
// import 'package:didvan/config/theme_data.dart';
|
||||||
import 'package:didvan/constants/app_icons.dart';
|
// import 'package:didvan/constants/app_icons.dart';
|
||||||
import 'package:didvan/constants/assets.dart';
|
// import 'package:didvan/constants/assets.dart';
|
||||||
import 'package:didvan/pages/home/direct/direct_state.dart';
|
// import 'package:didvan/pages/home/direct/direct_state.dart';
|
||||||
import 'package:didvan/services/media/media.dart';
|
// import 'package:didvan/services/media/media.dart';
|
||||||
import 'package:didvan/services/storage/storage.dart';
|
// import 'package:didvan/services/storage/storage.dart';
|
||||||
import 'package:didvan/utils/date_time.dart';
|
// import 'package:didvan/utils/date_time.dart';
|
||||||
import 'package:didvan/widgets/didvan/icon_button.dart';
|
// import 'package:didvan/widgets/didvan/icon_button.dart';
|
||||||
import 'package:didvan/widgets/didvan/text.dart';
|
// import 'package:didvan/widgets/didvan/text.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
// import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
// import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
// import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:just_waveform/just_waveform.dart';
|
// import 'package:just_waveform/just_waveform.dart';
|
||||||
import 'package:provider/provider.dart';
|
// import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class AudioVisualizer extends StatefulWidget {
|
// class AudioVisualizer extends StatefulWidget {
|
||||||
final File? audioFile;
|
// final File? audioFile;
|
||||||
final Waveform? waveform;
|
// final Waveform? waveform;
|
||||||
final String? audioUrl;
|
// final String? audioUrl;
|
||||||
final int? duration;
|
// final int? duration;
|
||||||
final Color? backgroundColor;
|
// final Color? backgroundColor;
|
||||||
|
|
||||||
const AudioVisualizer({
|
// const AudioVisualizer({
|
||||||
Key? key,
|
// Key? key,
|
||||||
this.audioFile,
|
// this.audioFile,
|
||||||
this.waveform,
|
// this.waveform,
|
||||||
this.audioUrl,
|
// this.audioUrl,
|
||||||
this.duration,
|
// this.duration,
|
||||||
this.backgroundColor,
|
// this.backgroundColor,
|
||||||
}) : super(key: key);
|
// }) : super(key: key);
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
State<AudioVisualizer> createState() => _AudioVisualizerState();
|
// State<AudioVisualizer> createState() => _AudioVisualizerState();
|
||||||
}
|
// }
|
||||||
|
|
||||||
class _AudioVisualizerState extends State<AudioVisualizer> {
|
// class _AudioVisualizerState extends State<AudioVisualizer> {
|
||||||
Stream<WaveformProgress>? waveDataStream;
|
// Stream<WaveformProgress>? waveDataStream;
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
void initState() {
|
// void initState() {
|
||||||
if (!kIsWeb && widget.audioFile != null) {
|
// if (!kIsWeb && widget.audioFile != null) {
|
||||||
waveDataStream = JustWaveform.extract(
|
// waveDataStream = JustWaveform.extract(
|
||||||
audioInFile: widget.audioFile!,
|
// audioInFile: widget.audioFile!,
|
||||||
waveOutFile: File(StorageService.appTempsDir + '/rec-wave.wave'),
|
// waveOutFile: File(StorageService.appTempsDir + '/rec-wave.wave'),
|
||||||
zoom: const WaveformZoom.pixelsPerSecond(100),
|
// zoom: const WaveformZoom.pixelsPerSecond(100),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
super.initState();
|
// super.initState();
|
||||||
}
|
// }
|
||||||
|
|
||||||
bool get _nowPlaying =>
|
// bool get _nowPlaying =>
|
||||||
MediaService.lastAudioPath == widget.audioFile ||
|
// MediaService.lastAudioPath == widget.audioFile ||
|
||||||
MediaService.lastAudioPath == widget.audioUrl;
|
// MediaService.lastAudioPath == widget.audioUrl;
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
Widget build(BuildContext context) {
|
// Widget build(BuildContext context) {
|
||||||
return Container(
|
// return Container(
|
||||||
decoration: BoxDecoration(
|
// decoration: BoxDecoration(
|
||||||
color: widget.backgroundColor ??
|
// color: widget.backgroundColor ??
|
||||||
(DesignConfig.isDark
|
// (DesignConfig.isDark
|
||||||
? Theme.of(context).colorScheme.black
|
// ? Theme.of(context).colorScheme.black
|
||||||
: Theme.of(context).colorScheme.background),
|
// : Theme.of(context).colorScheme.background),
|
||||||
borderRadius: DesignConfig.mediumBorderRadius,
|
// borderRadius: DesignConfig.mediumBorderRadius,
|
||||||
),
|
// ),
|
||||||
child: Row(
|
// child: Row(
|
||||||
children: [
|
// children: [
|
||||||
const SizedBox(width: 12),
|
// const SizedBox(width: 12),
|
||||||
StreamBuilder<Duration>(
|
// StreamBuilder<Duration>(
|
||||||
stream:
|
// stream:
|
||||||
_nowPlaying ? MediaService.audioPlayer.positionStream : null,
|
// _nowPlaying ? MediaService.audioPlayer.positionStream : null,
|
||||||
builder: (context, snapshot) {
|
// builder: (context, snapshot) {
|
||||||
String text = '';
|
// String text = '';
|
||||||
if (MediaService.audioPlayer.duration == null) {
|
// if (MediaService.audioPlayer.duration == null) {
|
||||||
Future.delayed(Duration.zero, () {
|
// Future.delayed(Duration.zero, () {
|
||||||
if (mounted) {
|
// if (mounted) {
|
||||||
setState(() {});
|
// setState(() {});
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
if (snapshot.data == null || snapshot.data == Duration.zero) {
|
// if (snapshot.data == null || snapshot.data == Duration.zero) {
|
||||||
text = DateTimeUtils.normalizeTimeDuration(
|
// text = DateTimeUtils.normalizeTimeDuration(
|
||||||
MediaService.audioPlayer.duration ??
|
// MediaService.audioPlayer.duration ??
|
||||||
widget.waveform?.duration ??
|
// widget.waveform?.duration ??
|
||||||
Duration.zero);
|
// Duration.zero);
|
||||||
} else {
|
// } else {
|
||||||
text = DateTimeUtils.normalizeTimeDuration(snapshot.data!);
|
// text = DateTimeUtils.normalizeTimeDuration(snapshot.data!);
|
||||||
}
|
// }
|
||||||
return DidvanText(
|
// return DidvanText(
|
||||||
text,
|
// text,
|
||||||
color: Theme.of(context).colorScheme.focusedBorder,
|
// color: Theme.of(context).colorScheme.focusedBorder,
|
||||||
isEnglishFont: true,
|
// isEnglishFont: true,
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 12),
|
// const SizedBox(width: 12),
|
||||||
Expanded(
|
// Expanded(
|
||||||
child: Builder(
|
// child: Builder(
|
||||||
builder: (context) {
|
// builder: (context) {
|
||||||
if (kIsWeb) {
|
// if (kIsWeb) {
|
||||||
return SvgPicture.asset(Assets.record);
|
// return SvgPicture.asset(Assets.record);
|
||||||
}
|
// }
|
||||||
if (widget.audioFile != null) {
|
// if (widget.audioFile != null) {
|
||||||
return StreamBuilder<WaveformProgress>(
|
// return StreamBuilder<WaveformProgress>(
|
||||||
stream: waveDataStream,
|
// stream: waveDataStream,
|
||||||
builder: (context, snapshot) {
|
// builder: (context, snapshot) {
|
||||||
if (snapshot.data == null ||
|
// if (snapshot.data == null ||
|
||||||
snapshot.data!.waveform == null) {
|
// snapshot.data!.waveform == null) {
|
||||||
return const SizedBox();
|
// return const SizedBox();
|
||||||
}
|
// }
|
||||||
final waveform = snapshot.data!.waveform!;
|
// final waveform = snapshot.data!.waveform!;
|
||||||
context.read<DirectState>().waveform = waveform;
|
// context.read<DirectState>().waveform = waveform;
|
||||||
return _waveWidget(waveform);
|
// return _waveWidget(waveform);
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
if (widget.waveform == null && waveDataStream == null) {
|
// if (widget.waveform == null && waveDataStream == null) {
|
||||||
return SvgPicture.asset(Assets.record);
|
// return SvgPicture.asset(Assets.record);
|
||||||
}
|
// }
|
||||||
return _waveWidget(widget.waveform!);
|
// return _waveWidget(widget.waveform!);
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
StreamBuilder<bool>(
|
// StreamBuilder<bool>(
|
||||||
stream: _nowPlaying ? MediaService.audioPlayer.playingStream : null,
|
// stream: _nowPlaying ? MediaService.audioPlayer.playingStream : null,
|
||||||
builder: (context, snapshot) {
|
// builder: (context, snapshot) {
|
||||||
return DidvanIconButton(
|
// return DidvanIconButton(
|
||||||
icon: snapshot.data == true
|
// icon: snapshot.data == true
|
||||||
? DidvanIcons.pause_circle_solid
|
// ? DidvanIcons.pause_circle_solid
|
||||||
: DidvanIcons.play_circle_solid,
|
// : DidvanIcons.play_circle_solid,
|
||||||
color: Theme.of(context).colorScheme.focusedBorder,
|
// color: Theme.of(context).colorScheme.focusedBorder,
|
||||||
onPressed: () {
|
// onPressed: () {
|
||||||
MediaService.handleAudioPlayback(
|
// MediaService.handleAudioPlayback(
|
||||||
audioSource: widget.audioFile ?? widget.audioUrl,
|
// audioSource: widget.audioFile ?? widget.audioUrl,
|
||||||
isNetworkAudio: widget.audioFile == null,
|
// isNetworkAudio: widget.audioFile == null,
|
||||||
);
|
// );
|
||||||
setState(() {});
|
// setState(() {});
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
Widget _waveWidget(Waveform waveform) => IgnorePointer(
|
// Widget _waveWidget(Waveform waveform) => IgnorePointer(
|
||||||
ignoring: !_nowPlaying,
|
// ignoring: !_nowPlaying,
|
||||||
child: GestureDetector(
|
// child: GestureDetector(
|
||||||
onHorizontalDragUpdate: _changePosition,
|
// onHorizontalDragUpdate: _changePosition,
|
||||||
onTapDown: _changePosition,
|
// onTapDown: _changePosition,
|
||||||
child: SizedBox(
|
// child: SizedBox(
|
||||||
height: double.infinity,
|
// height: double.infinity,
|
||||||
width: double.infinity,
|
// width: double.infinity,
|
||||||
child: _AudioWaveformWidget(
|
// child: _AudioWaveformWidget(
|
||||||
waveform: waveform,
|
// waveform: waveform,
|
||||||
start: Duration.zero,
|
// start: Duration.zero,
|
||||||
scale: 2,
|
// scale: 2,
|
||||||
strokeWidth: 3,
|
// strokeWidth: 3,
|
||||||
nowPlaying: _nowPlaying,
|
// nowPlaying: _nowPlaying,
|
||||||
duration: waveform.duration,
|
// duration: waveform.duration,
|
||||||
waveColor: Theme.of(context).colorScheme.focusedBorder,
|
// waveColor: Theme.of(context).colorScheme.focusedBorder,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
|
|
||||||
void _changePosition(details) {
|
// void _changePosition(details) {
|
||||||
if (MediaService.audioPlayer.audioSource == null) return;
|
// if (MediaService.audioPlayer.audioSource == null) return;
|
||||||
double posper =
|
// double posper =
|
||||||
details.localPosition.dx / (MediaQuery.of(context).size.width - 200);
|
// details.localPosition.dx / (MediaQuery.of(context).size.width - 200);
|
||||||
if (posper >= 1 || posper < 0) return;
|
// if (posper >= 1 || posper < 0) return;
|
||||||
final position = MediaService.audioPlayer.duration!.inMilliseconds;
|
// final position = MediaService.audioPlayer.duration!.inMilliseconds;
|
||||||
MediaService.audioPlayer.seek(
|
// MediaService.audioPlayer.seek(
|
||||||
Duration(milliseconds: (posper * position).toInt()),
|
// Duration(milliseconds: (posper * position).toInt()),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
class _AudioWaveformWidget extends StatelessWidget {
|
// class _AudioWaveformWidget extends StatelessWidget {
|
||||||
final Color waveColor;
|
// final Color waveColor;
|
||||||
final double scale;
|
// final double scale;
|
||||||
final double strokeWidth;
|
// final double strokeWidth;
|
||||||
final double pixelsPerStep;
|
// final double pixelsPerStep;
|
||||||
final Waveform waveform;
|
// final Waveform waveform;
|
||||||
final Duration start;
|
// final Duration start;
|
||||||
final bool nowPlaying;
|
// final bool nowPlaying;
|
||||||
final Duration duration;
|
// final Duration duration;
|
||||||
|
|
||||||
const _AudioWaveformWidget({
|
// const _AudioWaveformWidget({
|
||||||
Key? key,
|
// Key? key,
|
||||||
required this.waveform,
|
// required this.waveform,
|
||||||
required this.start,
|
// required this.start,
|
||||||
required this.duration,
|
// required this.duration,
|
||||||
required this.nowPlaying,
|
// required this.nowPlaying,
|
||||||
this.waveColor = Colors.blue,
|
// this.waveColor = Colors.blue,
|
||||||
this.scale = 1.0,
|
// this.scale = 1.0,
|
||||||
this.strokeWidth = 5.0,
|
// this.strokeWidth = 5.0,
|
||||||
this.pixelsPerStep = 8.0,
|
// this.pixelsPerStep = 8.0,
|
||||||
}) : super(key: key);
|
// }) : super(key: key);
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
Widget build(BuildContext context) {
|
// Widget build(BuildContext context) {
|
||||||
return ClipRect(
|
// return ClipRect(
|
||||||
child: StreamBuilder<Duration?>(
|
// child: StreamBuilder<Duration?>(
|
||||||
stream: nowPlaying ? MediaService.audioPlayer.positionStream : null,
|
// stream: nowPlaying ? MediaService.audioPlayer.positionStream : null,
|
||||||
builder: (context, snapshot) {
|
// builder: (context, snapshot) {
|
||||||
double progress = 0;
|
// double progress = 0;
|
||||||
if (snapshot.data == null ||
|
// if (snapshot.data == null ||
|
||||||
MediaService.audioPlayer.duration == null) {
|
// MediaService.audioPlayer.duration == null) {
|
||||||
progress = 0;
|
// progress = 0;
|
||||||
} else {
|
// } else {
|
||||||
progress = snapshot.data!.inMilliseconds /
|
// progress = snapshot.data!.inMilliseconds /
|
||||||
MediaService.audioPlayer.duration!.inMilliseconds *
|
// MediaService.audioPlayer.duration!.inMilliseconds *
|
||||||
100;
|
// 100;
|
||||||
}
|
// }
|
||||||
if (progress >= 100) {
|
// if (progress >= 100) {
|
||||||
progress = 0;
|
// progress = 0;
|
||||||
MediaService.audioPlayer.stop();
|
// MediaService.audioPlayer.stop();
|
||||||
MediaService.audioPlayer.seek(Duration.zero);
|
// MediaService.audioPlayer.seek(Duration.zero);
|
||||||
}
|
// }
|
||||||
return CustomPaint(
|
// return CustomPaint(
|
||||||
painter: _AudioWaveformPainter(
|
// painter: _AudioWaveformPainter(
|
||||||
waveColor: waveColor,
|
// waveColor: waveColor,
|
||||||
waveform: waveform,
|
// waveform: waveform,
|
||||||
start: start,
|
// start: start,
|
||||||
duration: duration,
|
// duration: duration,
|
||||||
scale: scale,
|
// scale: scale,
|
||||||
strokeWidth: strokeWidth,
|
// strokeWidth: strokeWidth,
|
||||||
pixelsPerStep: pixelsPerStep,
|
// pixelsPerStep: pixelsPerStep,
|
||||||
progressPercentage: progress,
|
// progressPercentage: progress,
|
||||||
progressColor: Theme.of(context).colorScheme.focusedBorder,
|
// progressColor: Theme.of(context).colorScheme.focusedBorder,
|
||||||
color: Theme.of(context).colorScheme.border,
|
// color: Theme.of(context).colorScheme.border,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}),
|
// }),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
class _AudioWaveformPainter extends CustomPainter {
|
// class _AudioWaveformPainter extends CustomPainter {
|
||||||
final double scale;
|
// final double scale;
|
||||||
final double strokeWidth;
|
// final double strokeWidth;
|
||||||
final double pixelsPerStep;
|
// final double pixelsPerStep;
|
||||||
final Waveform waveform;
|
// final Waveform waveform;
|
||||||
final Duration start;
|
// final Duration start;
|
||||||
final Duration duration;
|
// final Duration duration;
|
||||||
final double progressPercentage;
|
// final double progressPercentage;
|
||||||
final Color progressColor;
|
// final Color progressColor;
|
||||||
final Color color;
|
// final Color color;
|
||||||
|
|
||||||
_AudioWaveformPainter({
|
// _AudioWaveformPainter({
|
||||||
required this.waveform,
|
// required this.waveform,
|
||||||
required this.start,
|
// required this.start,
|
||||||
required this.duration,
|
// required this.duration,
|
||||||
required this.progressPercentage,
|
// required this.progressPercentage,
|
||||||
required this.color,
|
// required this.color,
|
||||||
required this.progressColor,
|
// required this.progressColor,
|
||||||
Color waveColor = Colors.blue,
|
// Color waveColor = Colors.blue,
|
||||||
this.scale = 1.0,
|
// this.scale = 1.0,
|
||||||
this.strokeWidth = 5.0,
|
// this.strokeWidth = 5.0,
|
||||||
this.pixelsPerStep = 8.0,
|
// this.pixelsPerStep = 8.0,
|
||||||
});
|
// });
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
void paint(Canvas canvas, Size size) {
|
// void paint(Canvas canvas, Size size) {
|
||||||
if (duration == Duration.zero) return;
|
// if (duration == Duration.zero) return;
|
||||||
double width = size.width;
|
// double width = size.width;
|
||||||
double height = size.height;
|
// double height = size.height;
|
||||||
|
|
||||||
final waveformPixelsPerWindow = waveform.positionToPixel(duration).toInt();
|
// final waveformPixelsPerWindow = waveform.positionToPixel(duration).toInt();
|
||||||
final waveformPixelsPerDevicePixel = waveformPixelsPerWindow / width;
|
// final waveformPixelsPerDevicePixel = waveformPixelsPerWindow / width;
|
||||||
final waveformPixelsPerStep = waveformPixelsPerDevicePixel * pixelsPerStep;
|
// final waveformPixelsPerStep = waveformPixelsPerDevicePixel * pixelsPerStep;
|
||||||
final sampleOffset = waveform.positionToPixel(start);
|
// final sampleOffset = waveform.positionToPixel(start);
|
||||||
final sampleStart = -sampleOffset % waveformPixelsPerStep;
|
// final sampleStart = -sampleOffset % waveformPixelsPerStep;
|
||||||
final totalLength = waveformPixelsPerWindow;
|
// final totalLength = waveformPixelsPerWindow;
|
||||||
final wavePaintB = Paint()
|
// final wavePaintB = Paint()
|
||||||
..style = PaintingStyle.stroke
|
// ..style = PaintingStyle.stroke
|
||||||
..strokeWidth = strokeWidth
|
// ..strokeWidth = strokeWidth
|
||||||
..strokeCap = StrokeCap.round
|
// ..strokeCap = StrokeCap.round
|
||||||
..color = progressColor;
|
// ..color = progressColor;
|
||||||
final wavePaintA = Paint()
|
// final wavePaintA = Paint()
|
||||||
..style = PaintingStyle.stroke
|
// ..style = PaintingStyle.stroke
|
||||||
..strokeWidth = strokeWidth
|
// ..strokeWidth = strokeWidth
|
||||||
..strokeCap = StrokeCap.round
|
// ..strokeCap = StrokeCap.round
|
||||||
..color = color;
|
// ..color = color;
|
||||||
for (var i = sampleStart.toDouble();
|
// for (var i = sampleStart.toDouble();
|
||||||
i <= waveformPixelsPerWindow + 1.0;
|
// i <= waveformPixelsPerWindow + 1.0;
|
||||||
i += waveformPixelsPerStep) {
|
// i += waveformPixelsPerStep) {
|
||||||
final sampleIdx = (sampleOffset + i).toInt();
|
// final sampleIdx = (sampleOffset + i).toInt();
|
||||||
final x = i / waveformPixelsPerDevicePixel;
|
// final x = i / waveformPixelsPerDevicePixel;
|
||||||
final minY = normalise(waveform.getPixelMin(sampleIdx), height);
|
// final minY = normalise(waveform.getPixelMin(sampleIdx), height);
|
||||||
final maxY = normalise(waveform.getPixelMax(sampleIdx), height);
|
// final maxY = normalise(waveform.getPixelMax(sampleIdx), height);
|
||||||
canvas.drawLine(
|
// canvas.drawLine(
|
||||||
Offset(x + strokeWidth / 2, max(strokeWidth * 0.75, minY)),
|
// Offset(x + strokeWidth / 2, max(strokeWidth * 0.75, minY)),
|
||||||
Offset(x + strokeWidth / 2, min(height - strokeWidth * 0.75, maxY)),
|
// Offset(x + strokeWidth / 2, min(height - strokeWidth * 0.75, maxY)),
|
||||||
i / totalLength < progressPercentage / 100 ? wavePaintB : wavePaintA,
|
// i / totalLength < progressPercentage / 100 ? wavePaintB : wavePaintA,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
bool shouldRepaint(covariant _AudioWaveformPainter oldDelegate) {
|
// bool shouldRepaint(covariant _AudioWaveformPainter oldDelegate) {
|
||||||
return oldDelegate.progressPercentage != progressPercentage;
|
// return oldDelegate.progressPercentage != progressPercentage;
|
||||||
}
|
// }
|
||||||
|
|
||||||
double normalise(int s, double height) {
|
// double normalise(int s, double height) {
|
||||||
final y = 32768 + (scale * s).clamp(-32768.0, 32767.0).toDouble();
|
// final y = 32768 + (scale * s).clamp(-32768.0, 32767.0).toDouble();
|
||||||
return height - 1 - y * height / 65536;
|
// return height - 1 - y * height / 65536;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:didvan/config/design_config.dart';
|
import 'package:didvan/config/design_config.dart';
|
||||||
import 'package:didvan/config/theme_data.dart';
|
import 'package:didvan/config/theme_data.dart';
|
||||||
import 'package:didvan/constants/app_icons.dart';
|
import 'package:didvan/constants/app_icons.dart';
|
||||||
import 'package:didvan/models/category.dart';
|
import 'package:didvan/models/message_data/radar_attachment.dart';
|
||||||
import 'package:didvan/models/view/action_sheet_data.dart';
|
import 'package:didvan/models/view/action_sheet_data.dart';
|
||||||
import 'package:didvan/pages/home/widgets/menu_item.dart';
|
import 'package:didvan/pages/home/widgets/menu_item.dart';
|
||||||
import 'package:didvan/routes/routes.dart';
|
import 'package:didvan/routes/routes.dart';
|
||||||
|
|
@ -16,14 +16,10 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class FloatingNavigationBar extends StatefulWidget {
|
class FloatingNavigationBar extends StatefulWidget {
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
|
final dynamic item;
|
||||||
final bool hasUnmarkConfirmation;
|
final bool hasUnmarkConfirmation;
|
||||||
final void Function(int count) onCommentsChanged;
|
final void Function(int count) onCommentsChanged;
|
||||||
final bool isRadar;
|
final bool isRadar;
|
||||||
final bool marked;
|
|
||||||
final int comments;
|
|
||||||
final int id;
|
|
||||||
final String title;
|
|
||||||
final List<CategoryData>? categories;
|
|
||||||
final void Function(bool value) onMarkChanged;
|
final void Function(bool value) onMarkChanged;
|
||||||
|
|
||||||
const FloatingNavigationBar({
|
const FloatingNavigationBar({
|
||||||
|
|
@ -32,11 +28,7 @@ class FloatingNavigationBar extends StatefulWidget {
|
||||||
required this.onCommentsChanged,
|
required this.onCommentsChanged,
|
||||||
required this.onMarkChanged,
|
required this.onMarkChanged,
|
||||||
required this.isRadar,
|
required this.isRadar,
|
||||||
required this.marked,
|
required this.item,
|
||||||
required this.comments,
|
|
||||||
required this.id,
|
|
||||||
required this.title,
|
|
||||||
this.categories,
|
|
||||||
this.hasUnmarkConfirmation = false,
|
this.hasUnmarkConfirmation = false,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
|
@ -50,7 +42,7 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant FloatingNavigationBar oldWidget) {
|
void didUpdateWidget(covariant FloatingNavigationBar oldWidget) {
|
||||||
_comments = widget.comments;
|
_comments = widget.item.comments;
|
||||||
_isScrolled = false;
|
_isScrolled = false;
|
||||||
_handleScroll();
|
_handleScroll();
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|
@ -60,7 +52,7 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
|
||||||
void initState() {
|
void initState() {
|
||||||
_handleScroll();
|
_handleScroll();
|
||||||
_isScrolled = false;
|
_isScrolled = false;
|
||||||
_comments = widget.comments;
|
_comments = widget.item.comments;
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,7 +105,7 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
|
||||||
if (widget.isRadar)
|
if (widget.isRadar)
|
||||||
BookmarkButton(
|
BookmarkButton(
|
||||||
askForConfirmation: widget.hasUnmarkConfirmation,
|
askForConfirmation: widget.hasUnmarkConfirmation,
|
||||||
value: widget.marked,
|
value: widget.item.marked,
|
||||||
onMarkChanged: (value) {
|
onMarkChanged: (value) {
|
||||||
widget.onMarkChanged(value);
|
widget.onMarkChanged(value);
|
||||||
if (widget.hasUnmarkConfirmation && !value) {
|
if (widget.hasUnmarkConfirmation && !value) {
|
||||||
|
|
@ -137,9 +129,9 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
|
||||||
onPressed: () => Navigator.of(context).pushNamed(
|
onPressed: () => Navigator.of(context).pushNamed(
|
||||||
Routes.comments,
|
Routes.comments,
|
||||||
arguments: {
|
arguments: {
|
||||||
'id': widget.id,
|
'id': widget.item.id,
|
||||||
'isRadar': widget.isRadar,
|
'isRadar': widget.isRadar,
|
||||||
'title': widget.title,
|
'title': widget.item.title,
|
||||||
'onCommentsChanged': widget.onCommentsChanged,
|
'onCommentsChanged': widget.onCommentsChanged,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -152,7 +144,7 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
|
||||||
if (!widget.isRadar)
|
if (!widget.isRadar)
|
||||||
BookmarkButton(
|
BookmarkButton(
|
||||||
askForConfirmation: widget.hasUnmarkConfirmation,
|
askForConfirmation: widget.hasUnmarkConfirmation,
|
||||||
value: widget.marked,
|
value: widget.item.marked,
|
||||||
onMarkChanged: (value) {
|
onMarkChanged: (value) {
|
||||||
widget.onMarkChanged(value);
|
widget.onMarkChanged(value);
|
||||||
if (widget.hasUnmarkConfirmation && !value) {
|
if (widget.hasUnmarkConfirmation && !value) {
|
||||||
|
|
@ -191,7 +183,7 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showMoreOptions() {
|
void _showMoreOptions() {
|
||||||
final categories = widget.categories!;
|
final categories = widget.item.categories!;
|
||||||
ActionSheetUtils.showBottomSheet(
|
ActionSheetUtils.showBottomSheet(
|
||||||
data: ActionSheetData(
|
data: ActionSheetData(
|
||||||
content: Column(
|
content: Column(
|
||||||
|
|
@ -211,7 +203,19 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
Navigator.of(context).pushNamed(
|
Navigator.of(context).pushNamed(
|
||||||
Routes.direct,
|
Routes.direct,
|
||||||
arguments: categories[i].id,
|
arguments: {
|
||||||
|
'radarAttachment': RadarAttachment(
|
||||||
|
id: widget.item.id,
|
||||||
|
title: widget.item.title,
|
||||||
|
description: widget.item.contents.first.text,
|
||||||
|
timeToRead: widget.item.timeToRead,
|
||||||
|
image: widget.item.image,
|
||||||
|
forManagers: widget.item.forManagers,
|
||||||
|
categories: widget.item.categories,
|
||||||
|
createdAt: widget.item.createdAt,
|
||||||
|
),
|
||||||
|
'type': categories[i].label,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -229,7 +233,7 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
Navigator.of(context).pushNamed(
|
Navigator.of(context).pushNamed(
|
||||||
Routes.direct,
|
Routes.direct,
|
||||||
arguments: 0,
|
arguments: {},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: DidvanIcons.description_regular,
|
icon: DidvanIcons.description_regular,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:didvan/config/theme_data.dart';
|
||||||
|
import 'package:didvan/constants/app_icons.dart';
|
||||||
|
import 'package:didvan/services/media/media.dart';
|
||||||
|
import 'package:didvan/widgets/didvan/icon_button.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AudioControllerButton extends StatelessWidget {
|
||||||
|
final String? audioUrl;
|
||||||
|
final File? audioFile;
|
||||||
|
|
||||||
|
const AudioControllerButton({Key? key, this.audioUrl, this.audioFile})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
bool get _nowPlaying =>
|
||||||
|
MediaService.audioPlayerTag == audioUrl ||
|
||||||
|
audioFile != null && MediaService.audioPlayerTag == audioFile!.path;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DidvanIconButton(
|
||||||
|
icon: MediaService.audioPlayer.playing == true && _nowPlaying
|
||||||
|
? DidvanIcons.pause_circle_solid
|
||||||
|
: DidvanIcons.play_circle_solid,
|
||||||
|
color: Theme.of(context).colorScheme.focusedBorder,
|
||||||
|
onPressed: () {
|
||||||
|
MediaService.handleAudioPlayback(
|
||||||
|
audioSource: audioFile ?? audioUrl,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,8 +8,9 @@ class ServerDataProvider {
|
||||||
await _getDirectTypes();
|
await _getDirectTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
static int labelToTypeId(String label) =>
|
static int labelToTypeId(String? label) => label == null
|
||||||
directTypes.firstWhere((element) => element.value == label).key;
|
? 7
|
||||||
|
: directTypes.firstWhere((element) => element.value.contains(label)).key;
|
||||||
|
|
||||||
static Future<void> _getDirectTypes() async {
|
static Future<void> _getDirectTypes() async {
|
||||||
final service = RequestService(RequestHelper.directTypes);
|
final service = RequestService(RequestHelper.directTypes);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:didvan/services/media/media.dart';
|
||||||
import 'package:didvan/services/storage/storage.dart';
|
import 'package:didvan/services/storage/storage.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -9,6 +10,7 @@ class AppInitializer {
|
||||||
StorageService.appDocsDir =
|
StorageService.appDocsDir =
|
||||||
(await getApplicationDocumentsDirectory()).path;
|
(await getApplicationDocumentsDirectory()).path;
|
||||||
StorageService.appTempsDir = (await getTemporaryDirectory()).path;
|
StorageService.appTempsDir = (await getTemporaryDirectory()).path;
|
||||||
|
MediaService.init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,36 @@ import 'package:just_audio/just_audio.dart';
|
||||||
|
|
||||||
class MediaService {
|
class MediaService {
|
||||||
static final AudioPlayer audioPlayer = AudioPlayer();
|
static final AudioPlayer audioPlayer = AudioPlayer();
|
||||||
static dynamic lastAudioPath;
|
static String? audioPlayerTag;
|
||||||
|
|
||||||
static Future<void> handleAudioPlayback(
|
static void init() {
|
||||||
{required dynamic audioSource, required bool isNetworkAudio}) async {
|
audioPlayer.positionStream.listen((event) {
|
||||||
if (lastAudioPath == audioSource) {
|
if (audioPlayer.duration != null && audioPlayer.duration! < event) {
|
||||||
|
audioPlayer.stop();
|
||||||
|
audioPlayer.seek(const Duration(seconds: 0));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> handleAudioPlayback({
|
||||||
|
required dynamic audioSource,
|
||||||
|
}) async {
|
||||||
|
bool isNetworkAudio = audioSource.runtimeType == String;
|
||||||
|
String tag;
|
||||||
|
if (isNetworkAudio) {
|
||||||
|
tag = audioSource;
|
||||||
|
} else {
|
||||||
|
tag = audioSource.path;
|
||||||
|
}
|
||||||
|
if (audioPlayerTag == tag) {
|
||||||
if (audioPlayer.playing) {
|
if (audioPlayer.playing) {
|
||||||
await audioPlayer.pause();
|
await audioPlayer.pause();
|
||||||
} else {
|
} else {
|
||||||
await audioPlayer.play();
|
await audioPlayer.play();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lastAudioPath = audioSource;
|
await audioPlayer.stop();
|
||||||
|
audioPlayerTag = tag;
|
||||||
if (isNetworkAudio) {
|
if (isNetworkAudio) {
|
||||||
await audioPlayer.setUrl(
|
await audioPlayer.setUrl(
|
||||||
RequestHelper.baseUrl +
|
RequestHelper.baseUrl +
|
||||||
|
|
@ -32,7 +50,7 @@ class MediaService {
|
||||||
await audioPlayer.setFilePath(audioSource.path);
|
await audioPlayer.setFilePath(audioSource.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await audioPlayer.play();
|
audioPlayer.play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
14
pubspec.lock
14
pubspec.lock
|
|
@ -15,6 +15,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.6+1"
|
version: "0.1.6+1"
|
||||||
|
audio_video_progress_bar:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: audio_video_progress_bar
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -357,13 +364,6 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.2"
|
version: "0.4.2"
|
||||||
just_waveform:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: just_waveform
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.0.1"
|
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ dependencies:
|
||||||
record: ^3.0.2
|
record: ^3.0.2
|
||||||
just_audio: ^0.9.18
|
just_audio: ^0.9.18
|
||||||
record_web: ^0.2.1
|
record_web: ^0.2.1
|
||||||
just_waveform: ^0.0.1
|
|
||||||
persian_datetime_picker: ^2.4.0
|
persian_datetime_picker: ^2.4.0
|
||||||
persian_number_utility: ^1.1.1
|
persian_number_utility: ^1.1.1
|
||||||
bot_toast: ^4.0.1
|
bot_toast: ^4.0.1
|
||||||
|
|
@ -61,6 +60,7 @@ dependencies:
|
||||||
crop: ^0.5.2
|
crop: ^0.5.2
|
||||||
url_launcher: ^6.0.18
|
url_launcher: ^6.0.18
|
||||||
transparent_image: ^2.0.0
|
transparent_image: ^2.0.0
|
||||||
|
audio_video_progress_bar: ^0.10.0
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue