mirror of
https://gitlab.com/comunic/comunicmobile
synced 2025-06-19 08:15:16 +00:00
Show message files
This commit is contained in:
135
lib/ui/widgets/audio_player_widget.dart
Normal file
135
lib/ui/widgets/audio_player_widget.dart
Normal file
@ -0,0 +1,135 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:comunic/utils/date_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Audio player widget
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
|
||||
class AudioPlayerWidget extends StatefulWidget {
|
||||
final File file;
|
||||
|
||||
const AudioPlayerWidget(this.file);
|
||||
|
||||
@override
|
||||
_AudioPlayerWidgetState createState() => _AudioPlayerWidgetState();
|
||||
}
|
||||
|
||||
class _AudioPlayerWidgetState extends State<AudioPlayerWidget> {
|
||||
AudioPlayer _player;
|
||||
|
||||
Duration _mediaDuration;
|
||||
Duration _mediaPosition;
|
||||
|
||||
double get _max => _mediaDuration?.inMilliseconds?.toDouble() ?? 0.0;
|
||||
|
||||
double get _value => _mediaPosition?.inMilliseconds?.toDouble() ?? 0.0;
|
||||
|
||||
bool get _playing =>
|
||||
_player != null && _player.state == AudioPlayerState.PLAYING;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
|
||||
if (_player != null) _player.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
textStyle: TextStyle(color: Colors.white),
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Spacer(),
|
||||
Icon(Icons.audiotrack, color: Colors.white),
|
||||
Spacer(),
|
||||
Slider(
|
||||
value: _value,
|
||||
onChanged: (newValue) =>
|
||||
_player.seek(Duration(milliseconds: newValue.toInt())),
|
||||
max: _max,
|
||||
activeColor: Colors.white,
|
||||
min: 0,
|
||||
),
|
||||
Spacer(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Spacer(),
|
||||
Text(formatDuration(_mediaPosition ?? Duration())),
|
||||
Spacer(),
|
||||
_AudioButton(
|
||||
icon: Icons.play_arrow, onTap: _play, visible: !_playing),
|
||||
_AudioButton(icon: Icons.pause, onTap: _pause, visible: _playing),
|
||||
_AudioButton(icon: Icons.stop, onTap: _stop, visible: _playing),
|
||||
Spacer(),
|
||||
Text(formatDuration(_mediaDuration ?? Duration())),
|
||||
Spacer(),
|
||||
],
|
||||
),
|
||||
Spacer(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _play() async {
|
||||
if (_player == null) {
|
||||
_player = AudioPlayer();
|
||||
|
||||
_player.onDurationChanged.listen((newDuration) {
|
||||
setState(() => _mediaDuration = newDuration);
|
||||
});
|
||||
|
||||
_player.onAudioPositionChanged.listen((newDuration) {
|
||||
setState(() => _mediaPosition = newDuration);
|
||||
});
|
||||
|
||||
_player.onPlayerStateChanged.listen((event) => setState(() {}));
|
||||
|
||||
_player.onSeekComplete.listen((event) => setState(() {}));
|
||||
}
|
||||
|
||||
if (_player.state != AudioPlayerState.PAUSED)
|
||||
_player.play(widget.file.absolute.path, isLocal: true);
|
||||
else
|
||||
_player.resume();
|
||||
}
|
||||
|
||||
void _pause() async {
|
||||
_player.pause();
|
||||
}
|
||||
|
||||
void _stop() {
|
||||
_player.stop();
|
||||
_player.seek(Duration());
|
||||
}
|
||||
}
|
||||
|
||||
class _AudioButton extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final void Function() onTap;
|
||||
final bool visible;
|
||||
|
||||
const _AudioButton({
|
||||
Key key,
|
||||
@required this.icon,
|
||||
@required this.onTap,
|
||||
@required this.visible,
|
||||
}) : assert(icon != null),
|
||||
assert(onTap != null),
|
||||
assert(visible != null),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!visible) return Container();
|
||||
|
||||
return IconButton(icon: Icon(icon, color: Colors.white), onPressed: onTap);
|
||||
}
|
||||
}
|
200
lib/ui/widgets/conversation_file_tile.dart
Normal file
200
lib/ui/widgets/conversation_file_tile.dart
Normal file
@ -0,0 +1,200 @@
|
||||
/// Chat file tile
|
||||
///
|
||||
/// @author Pierre Hubert
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:comunic/helpers/conversation_files_helper.dart';
|
||||
import 'package:comunic/models/conversation_message.dart';
|
||||
import 'package:comunic/ui/widgets/async_screen_widget.dart';
|
||||
import 'package:comunic/ui/widgets/audio_player_widget.dart';
|
||||
import 'package:comunic/ui/widgets/network_image_widget.dart';
|
||||
import 'package:comunic/utils/intl_utils.dart';
|
||||
import 'package:comunic/utils/log_utils.dart';
|
||||
import 'package:comunic/utils/ui_utils.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:filesize/filesize.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
const _AreaSize = 150.0;
|
||||
|
||||
class ConversationFileWidget extends StatefulWidget {
|
||||
final int messageID;
|
||||
final ConversationMessageFile file;
|
||||
|
||||
const ConversationFileWidget({
|
||||
Key key,
|
||||
@required this.messageID,
|
||||
@required this.file,
|
||||
}) : assert(messageID != null),
|
||||
assert(file != null),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
_ConversationFileWidgetState createState() => _ConversationFileWidgetState();
|
||||
}
|
||||
|
||||
class _ConversationFileWidgetState extends State<ConversationFileWidget> {
|
||||
final _refreshKey = GlobalKey<AsyncScreenWidgetState>();
|
||||
|
||||
File _targetFile;
|
||||
|
||||
bool _isDownloaded;
|
||||
|
||||
bool _downloading = false;
|
||||
var _downloadProgress = 0.0;
|
||||
CancelToken _cancelDownloadToken;
|
||||
|
||||
ConversationMessageFile get file => widget.file;
|
||||
|
||||
Future<void> _refresh() async {
|
||||
_targetFile = await ConversationFilesHelper.getPathForChatFile(
|
||||
widget.messageID, file);
|
||||
|
||||
_isDownloaded = await _targetFile.exists();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: _AreaSize,
|
||||
height: _AreaSize,
|
||||
child: AsyncScreenWidget(
|
||||
key: _refreshKey,
|
||||
onReload: _refresh,
|
||||
onBuild: _buildContent,
|
||||
errorMessage: tr("Error!"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent() => _isDownloaded || !file.downloadable
|
||||
? _buildFileWidget()
|
||||
: _buildDownloadWidget();
|
||||
|
||||
Widget _buildDownloadWidget() => Stack(
|
||||
children: <Widget>[
|
||||
// Thumbnail, if possible
|
||||
!file.hasThumbnail
|
||||
? Container()
|
||||
: CachedNetworkImage(
|
||||
imageUrl: file.thumbnail,
|
||||
width: _AreaSize,
|
||||
height: _AreaSize,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
|
||||
Container(
|
||||
width: _AreaSize,
|
||||
color: Color(0x66000000),
|
||||
child: DefaultTextStyle(
|
||||
style: TextStyle(color: Colors.white),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Spacer(),
|
||||
Icon(file.icon, color: Colors.white),
|
||||
Spacer(),
|
||||
_buildDownloadArea(),
|
||||
Spacer(),
|
||||
Text(filesize(file.size)),
|
||||
Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
Widget _buildDownloadArea() => _downloading
|
||||
? _buildDownloadingWidget()
|
||||
: Material(
|
||||
borderRadius: BorderRadius.all(Radius.circular(2.0)),
|
||||
color: Colors.green,
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.file_download),
|
||||
onPressed: _downloadFile,
|
||||
color: Colors.white,
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildDownloadingWidget() => Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
CircularProgressIndicator(value: _downloadProgress),
|
||||
Center(
|
||||
child: InkWell(
|
||||
onTap: () => _cancelDownloadToken.cancel(),
|
||||
child: Icon(Icons.cancel, color: Colors.white),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> _downloadFile() async {
|
||||
try {
|
||||
setState(() {
|
||||
_cancelDownloadToken = CancelToken();
|
||||
_downloading = true;
|
||||
_downloadProgress = 0.0;
|
||||
});
|
||||
|
||||
await ConversationFilesHelper.download(
|
||||
msgID: widget.messageID,
|
||||
fileInfo: file,
|
||||
onProgress: (p) => setState(() => _downloadProgress = p),
|
||||
cancelToken: _cancelDownloadToken,
|
||||
);
|
||||
|
||||
await _refreshKey.currentState.refresh();
|
||||
} catch (e, s) {
|
||||
logError(e, s);
|
||||
showSimpleSnack(context, tr("Failed to download file!"));
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_downloading = false;
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildFileWidget() {
|
||||
switch (file.fileType) {
|
||||
// Images
|
||||
case ConversationMessageFileType.IMAGE:
|
||||
return Center(
|
||||
child: NetworkImageWidget(
|
||||
url: file.url,
|
||||
thumbnailURL: file.thumbnail,
|
||||
allowFullScreen: true,
|
||||
),
|
||||
);
|
||||
|
||||
// Audio player
|
||||
case ConversationMessageFileType.AUDIO:
|
||||
return AudioPlayerWidget(_targetFile);
|
||||
|
||||
// The file is not downloadable, we open it in the browser
|
||||
default:
|
||||
return Center(
|
||||
child: MaterialButton(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Spacer(flex: 2),
|
||||
Icon(file.icon, color: Colors.white),
|
||||
Spacer(),
|
||||
Text(file.name, textAlign: TextAlign.center),
|
||||
Spacer(flex: 2),
|
||||
],
|
||||
),
|
||||
onPressed: () => launch(file.url),
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
class NetworkImageWidget extends StatelessWidget {
|
||||
final String url;
|
||||
final String thumbnailURL;
|
||||
final bool allowFullScreen;
|
||||
final bool roundedEdges;
|
||||
final double width;
|
||||
@ -19,6 +20,7 @@ class NetworkImageWidget extends StatelessWidget {
|
||||
const NetworkImageWidget({
|
||||
Key key,
|
||||
@required this.url,
|
||||
this.thumbnailURL,
|
||||
this.allowFullScreen = false,
|
||||
this.width,
|
||||
this.height,
|
||||
@ -42,7 +44,7 @@ class NetworkImageWidget extends StatelessWidget {
|
||||
}
|
||||
: null,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: url,
|
||||
imageUrl: thumbnailURL ?? url,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: BoxFit.cover,
|
||||
|
Reference in New Issue
Block a user