D1APP-53 direct (waiting for waveform descussoin)

This commit is contained in:
MohammadTaha Basiri 2022-02-09 12:00:47 +03:30
parent 2743e46634
commit 627e6ee2df
9 changed files with 303 additions and 151 deletions

View File

@ -1,3 +1,7 @@
import 'dart:io';
import 'package:just_waveform/just_waveform.dart';
import 'radar_attachment.dart'; import 'radar_attachment.dart';
class MessageData { class MessageData {
@ -8,6 +12,8 @@ class MessageData {
final bool readed; final bool readed;
final String createdAt; final String createdAt;
final RadarAttachment? radar; final RadarAttachment? radar;
final File? audioFile;
final Waveform? waveform;
const MessageData({ const MessageData({
required this.id, required this.id,
@ -17,6 +23,8 @@ class MessageData {
required this.text, required this.text,
required this.audio, required this.audio,
required this.radar, required this.radar,
required this.waveform,
this.audioFile,
}); });
factory MessageData.fromJson(Map<String, dynamic> json) => MessageData( factory MessageData.fromJson(Map<String, dynamic> json) => MessageData(
@ -26,6 +34,16 @@ class MessageData {
writedByAdmin: json['writedByAdmin'], writedByAdmin: json['writedByAdmin'],
readed: json['readed'], readed: json['readed'],
createdAt: json['createdAt'], createdAt: json['createdAt'],
waveform: json['waveForm'] != null
? Waveform(
version: json['version'],
flags: json['flags'],
sampleRate: json['sampleRate'],
samplesPerPixel: json['samplesPerPixel'],
length: json['length'],
data: json['data'],
)
: null,
radar: json['radar'] == null radar: json['radar'] == null
? null ? null
: RadarAttachment.fromJson(json['radar'] as Map<String, dynamic>), : RadarAttachment.fromJson(json['radar'] as Map<String, dynamic>),

View File

@ -2,11 +2,13 @@ import 'dart:io';
import 'package:didvan/models/enums.dart'; import 'package:didvan/models/enums.dart';
import 'package:didvan/models/message_data/message_data.dart'; import 'package:didvan/models/message_data/message_data.dart';
import 'package:didvan/models/message_data/radar_attachment.dart';
import 'package:didvan/providers/core_provider.dart'; import 'package:didvan/providers/core_provider.dart';
import 'package:didvan/services/network/request.dart'; 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 {
@ -15,7 +17,10 @@ class DirectState extends CoreProvier {
late final int typeId; late final int typeId;
final Map<String, List<int>> dailyMessages = {}; final Map<String, List<int>> dailyMessages = {};
String? text;
RadarAttachment? replyRadar;
File? recordedFile; File? recordedFile;
Waveform? waveform;
bool isRecording = false; bool isRecording = false;
@ -27,14 +32,7 @@ class DirectState extends CoreProvier {
final messageDatas = service.result['messages']; final messageDatas = service.result['messages'];
for (var i = 0; i < messageDatas.length; i++) { for (var i = 0; i < messageDatas.length; i++) {
messages.add(MessageData.fromJson(messageDatas[i])); messages.add(MessageData.fromJson(messageDatas[i]));
final createdAt = messages.last.createdAt.split('T').first; _addToDailyGrouped();
if (!dailyMessages.containsKey(createdAt)) {
dailyMessages.addAll({
createdAt: [messages.last.id]
});
} else {
dailyMessages[createdAt]!.add(messages.last.id);
}
} }
appState = AppState.idle; appState = AppState.idle;
return; return;
@ -58,7 +56,7 @@ class DirectState extends CoreProvier {
notifyListeners(); notifyListeners();
} }
Future<void> stopRecording(bool sendImidiately) async { Future<void> stopRecording({required bool sendImidiately}) async {
final path = await _recorder.stop(); final path = await _recorder.stop();
isRecording = false; isRecording = false;
if (path == null) { if (path == null) {
@ -78,5 +76,45 @@ class DirectState extends CoreProvier {
} }
} }
Future<void> sendMessage() async {} void _addToDailyGrouped() {
final createdAt = messages.last.createdAt.split('T').first;
if (!dailyMessages.containsKey(createdAt)) {
dailyMessages.addAll({
createdAt: [messages.last.id]
});
} else {
dailyMessages[createdAt]!.add(messages.last.id);
}
}
Future<void> sendMessage() async {
if (text == null || text!.isEmpty && recordedFile == null) return;
final body = {};
if (text != null) {
body.addAll({'text': text});
messages.add(
MessageData(
id: 0,
writedByAdmin: false,
readed: false,
createdAt: DateTime.now().toString(),
text: text,
audio: null,
audioFile: recordedFile,
radar: replyRadar,
waveform: waveform,
),
);
}
_addToDailyGrouped();
if (replyRadar != null) {
body.addAll({'radarId': replyRadar!.id});
}
text = null;
recordedFile = null;
notifyListeners();
final service =
RequestService(RequestHelper.sendDirectMessage(typeId), body: body);
await service.post();
}
} }

View File

@ -2,7 +2,9 @@ import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.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/utils/date_time.dart'; import 'package:didvan/utils/date_time.dart';
import 'package:didvan/widgets/didvan/divider.dart';
import 'package:didvan/widgets/didvan/text.dart'; import 'package:didvan/widgets/didvan/text.dart';
import 'package:didvan/widgets/skeleton_image.dart'; import 'package:didvan/widgets/skeleton_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -15,19 +17,18 @@ class Message extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( final firstMessageOfGroupId = context
padding: EdgeInsets.only(
right: message.writedByAdmin ? 20 : 0,
left: !message.writedByAdmin ? 20 : 0,
),
child: Column(
children: [
if (message.id ==
context
.read<DirectState>() .read<DirectState>()
.dailyMessages[message.createdAt.split('T').first]! .dailyMessages[message.createdAt.split('T').first]!
.last) .last;
Container( return Column(
crossAxisAlignment: message.writedByAdmin
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
children: [
if (message.id == firstMessageOfGroupId)
Center(
child: Container(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -42,23 +43,45 @@ class Message extends StatelessWidget {
: Theme.of(context).colorScheme.black, : Theme.of(context).colorScheme.black,
), ),
), ),
),
Padding(
padding: EdgeInsets.only(
right: message.writedByAdmin ? 20 : 0,
left: !message.writedByAdmin ? 20 : 0,
),
child: Column(
crossAxisAlignment: message.writedByAdmin
? CrossAxisAlignment.start
: CrossAxisAlignment.end,
children: [
_MessageContainer( _MessageContainer(
isAttachment: false,
writedByAdmin: message.writedByAdmin, writedByAdmin: message.writedByAdmin,
child: child: Column(
message.text != null ? DidvanText(message.text!) : Container(), children: [
), if (message.text != null) DidvanText(message.text!),
if (message.radar != null) if (message.audio != null)
DidvanText( AudioVisualizer(
'لینک به مطلب زیر:', audioUrl: message.audio,
style: Theme.of(context).textTheme.overline, waveform: message.waveform,
color: Theme.of(context).colorScheme.primary,
), ),
if (message.radar != null) const DidvanDivider(),
if (message.radar != null) const SizedBox(height: 4), if (message.radar != null) const SizedBox(height: 4),
if (message.radar != null) _ReplyRadarOverview(message: message), if (message.radar != null)
_ReplyRadarOverview(message: message),
if (message.radar != null) const SizedBox(height: 4), if (message.radar != null) const SizedBox(height: 4),
], ],
), ),
),
const SizedBox(height: 4),
DidvanText(
DateTimeUtils.timeWithAmPm(message.createdAt),
style: Theme.of(context).textTheme.overline,
color: Theme.of(context).colorScheme.caption,
),
],
),
),
],
); );
} }
} }
@ -70,21 +93,18 @@ class _ReplyRadarOverview extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return _MessageContainer( return Row(
writedByAdmin: message.writedByAdmin,
isAttachment: true,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SkeletonImage( SkeletonImage(
imageUrl: message.radar!.image, imageUrl: message.radar!.image,
height: 80, height: 52,
width: 80, width: 52,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: SizedBox( child: SizedBox(
height: 80, height: 52,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -92,7 +112,7 @@ class _ReplyRadarOverview extends StatelessWidget {
DidvanText( DidvanText(
message.radar!.title, message.radar!.title,
style: Theme.of(context).textTheme.bodyText1, style: Theme.of(context).textTheme.bodyText1,
maxLines: 2, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
color: Theme.of(context).colorScheme.focusedBorder, color: Theme.of(context).colorScheme.focusedBorder,
), ),
@ -105,8 +125,7 @@ class _ReplyRadarOverview extends StatelessWidget {
), ),
const Spacer(), const Spacer(),
DidvanText( DidvanText(
DateTimeUtils.momentGenerator( DateTimeUtils.momentGenerator(message.radar!.createdAt) +
message.radar!.createdAt) +
' | خواندن در ' + ' | خواندن در ' +
message.radar!.timeToRead.toString() + message.radar!.timeToRead.toString() +
' دقیقه', ' دقیقه',
@ -121,19 +140,16 @@ class _ReplyRadarOverview extends StatelessWidget {
), ),
), ),
], ],
),
); );
} }
} }
class _MessageContainer extends StatelessWidget { class _MessageContainer extends StatelessWidget {
final bool writedByAdmin; final bool writedByAdmin;
final bool isAttachment;
final Widget child; final Widget child;
const _MessageContainer({ const _MessageContainer({
Key? key, Key? key,
required this.writedByAdmin, required this.writedByAdmin,
required this.isAttachment,
required this.child, required this.child,
}) : super(key: key); }) : super(key: key);
@ -143,8 +159,8 @@ class _MessageContainer extends StatelessWidget {
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: DesignConfig.highBorderRadius.copyWith( borderRadius: DesignConfig.highBorderRadius.copyWith(
bottomLeft: writedByAdmin && !isAttachment ? Radius.zero : null, bottomLeft: writedByAdmin ? Radius.zero : null,
bottomRight: !writedByAdmin && !isAttachment ? Radius.zero : null, bottomRight: !writedByAdmin ? Radius.zero : null,
), ),
color: writedByAdmin ? null : Theme.of(context).colorScheme.focused, color: writedByAdmin ? null : Theme.of(context).colorScheme.focused,
border: Border.all( border: Border.all(

View File

@ -1,3 +1,4 @@
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';
@ -36,32 +37,69 @@ class MessageBox extends StatelessWidget {
} }
} }
class _Typing extends StatelessWidget { class _Typing extends StatefulWidget {
const _Typing({Key? key}) : super(key: key); const _Typing({Key? key}) : super(key: key);
@override
State<_Typing> createState() => _TypingState();
}
class _TypingState extends State<_Typing> {
final _formKey = GlobalKey<FormState>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final state = context.read<DirectState>(); final state = context.read<DirectState>();
return Row( return Row(
children: [ children: [
DidvanIconButton( Expanded(
flex: 2,
child: AnimatedSwitcher(
duration: DesignConfig.lowAnimationDuration,
transitionBuilder: (child, animation) => ScaleTransition(
scale: animation,
child: child,
),
child: state.text != null && state.text!.isNotEmpty
? DidvanIconButton(
key: const ValueKey(1),
icon: DidvanIcons.send_solid,
onPressed: () {
_formKey.currentState!.reset();
state.sendMessage();
},
size: 32,
color: Theme.of(context).colorScheme.focusedBorder,
)
: DidvanIconButton(
key: const ValueKey(2),
icon: DidvanIcons.mic_solid, icon: DidvanIcons.mic_solid,
onPressed: state.startRecording, onPressed: state.startRecording,
size: 32, size: 32,
color: Theme.of(context).colorScheme.focusedBorder, color: Theme.of(context).colorScheme.focusedBorder,
), ),
),
),
Expanded( Expanded(
flex: 15,
child: Form(
key: _formKey,
child: TextField( child: TextField(
textInputAction: TextInputAction.send, textInputAction: TextInputAction.send,
style: Theme.of(context).textTheme.bodyText2,
decoration: InputDecoration( decoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
hintText: 'بنویسید یا پیام صوتی بگذارید...', hintText: 'بنویسید یا پیام صوتی بگذارید...',
hintStyle: Theme.of(context) hintStyle: Theme.of(context).textTheme.caption!.copyWith(
.textTheme color: Theme.of(context).colorScheme.disabledText),
.caption! ),
.copyWith(color: Theme.of(context).colorScheme.disabledText), onChanged: (value) {
if (value.length <= 1) {
setState(() {});
}
state.text = value;
},
), ),
onChanged: (value) {},
), ),
), ),
], ],
@ -79,7 +117,7 @@ class _Recording extends StatelessWidget {
children: [ children: [
DidvanIconButton( DidvanIconButton(
icon: DidvanIcons.send_solid, icon: DidvanIcons.send_solid,
onPressed: () => state.stopRecording(true), onPressed: () => state.stopRecording(sendImidiately: true),
gestureSize: 52, gestureSize: 52,
), ),
Expanded( Expanded(
@ -91,7 +129,7 @@ class _Recording extends StatelessWidget {
DidvanIconButton( DidvanIconButton(
icon: DidvanIcons.stop_circle_solid, icon: DidvanIcons.stop_circle_solid,
color: Theme.of(context).colorScheme.secondary, color: Theme.of(context).colorScheme.secondary,
onPressed: () => state.stopRecording(false), onPressed: () => state.stopRecording(sendImidiately: false),
size: 32, size: 32,
), ),
], ],
@ -109,7 +147,7 @@ class _RecordChecking extends StatelessWidget {
children: [ children: [
DidvanIconButton( DidvanIconButton(
icon: DidvanIcons.send_solid, icon: DidvanIcons.send_solid,
onPressed: () => state.stopRecording(true), onPressed: () => state.stopRecording(sendImidiately: true),
color: Theme.of(context).colorScheme.focusedBorder, color: Theme.of(context).colorScheme.focusedBorder,
), ),
Expanded( Expanded(

View File

@ -5,6 +5,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/constants/assets.dart'; import 'package:didvan/constants/assets.dart';
import 'package:didvan/pages/home/direct/direct_state.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';
@ -14,13 +15,18 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
import 'package:just_waveform/just_waveform.dart'; import 'package:just_waveform/just_waveform.dart';
import 'package:provider/provider.dart';
class AudioVisualizer extends StatefulWidget { class AudioVisualizer extends StatefulWidget {
final File audioFile; final File? audioFile;
final Waveform? waveform;
final String? audioUrl;
const AudioVisualizer({ const AudioVisualizer({
Key? key, Key? key,
required this.audioFile, this.audioFile,
this.waveform,
this.audioUrl,
}) : super(key: key); }) : super(key: key);
@override @override
@ -34,9 +40,9 @@ class _AudioVisualizerState extends State<AudioVisualizer> {
@override @override
void initState() { void initState() {
if (!kIsWeb) { 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),
); );
@ -86,6 +92,8 @@ class _AudioVisualizerState extends State<AudioVisualizer> {
if (kIsWeb) { if (kIsWeb) {
return SvgPicture.asset(Assets.record); return SvgPicture.asset(Assets.record);
} }
if (widget.audioFile != null) {
return StreamBuilder<WaveformProgress>( return StreamBuilder<WaveformProgress>(
stream: waveDataStream, stream: waveDataStream,
builder: (context, snapshot) { builder: (context, snapshot) {
@ -96,26 +104,12 @@ class _AudioVisualizerState extends State<AudioVisualizer> {
return const SizedBox(); return const SizedBox();
} }
final waveform = snapshot.data!.waveform!; final waveform = snapshot.data!.waveform!;
return GestureDetector( context.read<DirectState>().waveform = waveform;
onHorizontalDragUpdate: _changePosition, return _waveWidget(waveform);
onTapDown: _changePosition,
child: SizedBox(
height: double.infinity,
width: double.infinity,
child: _AudioWaveformWidget(
waveform: waveform,
audioPlayer: _audioPlayer,
start: Duration.zero,
scale: 2,
strokeWidth: 3,
duration: waveform.duration,
waveColor:
Theme.of(context).colorScheme.focusedBorder,
),
),
);
}, },
); );
}
return _waveWidget(widget.waveform!);
}, },
), ),
), ),
@ -136,6 +130,24 @@ class _AudioVisualizerState extends State<AudioVisualizer> {
); );
} }
Widget _waveWidget(Waveform waveform) => GestureDetector(
onHorizontalDragUpdate: _changePosition,
onTapDown: _changePosition,
child: SizedBox(
height: double.infinity,
width: double.infinity,
child: _AudioWaveformWidget(
waveform: waveform,
audioPlayer: _audioPlayer,
start: Duration.zero,
scale: 2,
strokeWidth: 3,
duration: waveform.duration,
waveColor: Theme.of(context).colorScheme.focusedBorder,
),
),
);
void _changePosition(details) { void _changePosition(details) {
double posper = double posper =
details.localPosition.dx / (MediaQuery.of(context).size.width - 200); details.localPosition.dx / (MediaQuery.of(context).size.width - 200);
@ -148,12 +160,14 @@ class _AudioVisualizerState extends State<AudioVisualizer> {
} }
Future<void> _setupAudioPlayer() async { Future<void> _setupAudioPlayer() async {
if (kIsWeb) { if (kIsWeb || widget.audioFile == null) {
await _audioPlayer.setUrl( await _audioPlayer.setUrl(
widget.audioFile.uri.path.replaceAll('%3A', ':'), kIsWeb
? widget.audioFile!.uri.path.replaceAll('%3A', ':')
: widget.audioUrl!,
); );
} else { } else {
await _audioPlayer.setFilePath(widget.audioFile.path); await _audioPlayer.setFilePath(widget.audioFile!.path);
} }
} }

View File

@ -2,7 +2,7 @@ import 'package:didvan/pages/authentication/authentication.dart';
import 'package:didvan/pages/authentication/authentication_state.dart'; import 'package:didvan/pages/authentication/authentication_state.dart';
import 'package:didvan/pages/home/comments/comments.dart'; import 'package:didvan/pages/home/comments/comments.dart';
import 'package:didvan/pages/home/comments/comments_state.dart'; import 'package:didvan/pages/home/comments/comments_state.dart';
import 'package:didvan/pages/home/direct/widgets/direct.dart'; import 'package:didvan/pages/home/direct/direct.dart';
import 'package:didvan/pages/home/direct/direct_state.dart'; import 'package:didvan/pages/home/direct/direct_state.dart';
import 'package:didvan/pages/home/home.dart'; import 'package:didvan/pages/home/home.dart';
import 'package:didvan/pages/home/home_state.dart'; import 'package:didvan/pages/home/home_state.dart';

View File

@ -6,6 +6,7 @@ class RequestHelper {
static const String _baseUserUrl = baseUrl + '/user'; static const String _baseUserUrl = baseUrl + '/user';
static const String _baseRadarUrl = baseUrl + '/radar'; static const String _baseRadarUrl = baseUrl + '/radar';
static const String _baseNewsUrl = baseUrl + '/news'; static const String _baseNewsUrl = baseUrl + '/news';
static const String _baseDirectUrl = _baseUserUrl + '/direct';
static const String confirmUsername = _baseUserUrl + '/confirmUsername'; static const String confirmUsername = _baseUserUrl + '/confirmUsername';
static const String login = _baseUserUrl + '/login'; static const String login = _baseUserUrl + '/login';
@ -18,7 +19,9 @@ class RequestHelper {
_baseUserUrl + '/marked/${type ?? ''}'; _baseUserUrl + '/marked/${type ?? ''}';
static const String directTypes = baseUrl + '/direct/types'; static const String directTypes = baseUrl + '/direct/types';
static String direct(int id) => _baseUserUrl + '/direct/$id'; static String direct(int id) => _baseDirectUrl + '/$id';
static String sendDirectMessage(int id) =>
_baseDirectUrl + '/$id/sendMessage';
static String tag({ static String tag({
required List<int> ids, required List<int> ids,
required String type, required String type,

View File

@ -52,6 +52,31 @@ class DateTimeUtils {
return result?.toDateTime().toString(); return result?.toDateTime().toString();
} }
static String timeWithAmPm(String input) {
final dateTime = utcToLocalTime(input);
bool isAm = true;
int hour = 0;
int minute = 0;
if (dateTime.hour > 12) {
isAm = false;
hour = dateTime.hour - 12;
} else {
hour = dateTime.hour;
}
minute = dateTime.minute;
return '$hour:${_timeNormalizer(minute)} ${isAm ? 'ق.ظ' : 'ب.ظ'}';
}
static DateTime utcToLocalTime(String input) {
final dateTime = DateTime.parse(input);
return dateTime.add(const Duration(hours: 3, minutes: 30));
}
static String _timeNormalizer(int input) {
if (input < 10) return '0$input';
return input.toString();
}
static String momentGenerator(String input) { static String momentGenerator(String input) {
final date = DateTime.parse(input); final date = DateTime.parse(input);
final int seconds = (DateTime.now().difference(date).inSeconds).floor(); final int seconds = (DateTime.now().difference(date).inSeconds).floor();