Skip to content

Commit

Permalink
Merge branch 'develop' into feature/540984-improve-image-bubble
Browse files Browse the repository at this point in the history
  • Loading branch information
Andre Rossi committed Oct 20, 2023
2 parents 00e67b6 + 5c489fa commit f43658f
Show file tree
Hide file tree
Showing 16 changed files with 172 additions and 141 deletions.
25 changes: 0 additions & 25 deletions .github/workflows/merge.yml

This file was deleted.

5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.0.83

- [DSCard] Accept reply messages
- [DSMessageContentType] Add reply as content type

## 0.0.82

- [DSAudioPlayer] Improved the audio player initialization
Expand Down
9 changes: 5 additions & 4 deletions lib/blip_ds.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export 'src/themes/texts/styles/ds_headline_small_text_style.theme.dart'
export 'src/themes/texts/utils/ds_font_families.theme.dart' show DSFontFamilies;
export 'src/themes/texts/utils/ds_font_weights.theme.dart' show DSFontWeights;
export 'src/utils/ds_animate.util.dart' show DSAnimate;
export 'src/utils/ds_directory_formatter.util.dart' show DSDirectoryFormatter;
export 'src/utils/ds_linkify.util.dart' show DSLinkify;
export 'src/utils/ds_utils.util.dart' show DSUtils;
export 'src/widgets/animations/ds_animated_size.widget.dart'
Expand Down Expand Up @@ -77,10 +78,10 @@ export 'src/widgets/buttons/ds_icon_button.widget.dart' show DSIconButton;
export 'src/widgets/buttons/ds_pause_button.widget.dart' show DSPauseButton;
export 'src/widgets/buttons/ds_play_button.widget.dart' show DSPlayButton;
export 'src/widgets/buttons/ds_primary_button.widget.dart' show DSPrimaryButton;
export 'src/widgets/buttons/ds_secondary_button.widget.dart'
show DSSecondaryButton;
export 'src/widgets/buttons/ds_request_location_button.widget.dart'
show DSRequestLocationButton;
export 'src/widgets/buttons/ds_secondary_button.widget.dart'
show DSSecondaryButton;
export 'src/widgets/buttons/ds_send_button.widget.dart' show DSSendButton;
export 'src/widgets/buttons/ds_tertiary_button.widget.dart'
show DSTertiaryButton;
Expand All @@ -101,6 +102,8 @@ export 'src/widgets/chat/ds_location_message_bubble.widget.dart'
export 'src/widgets/chat/ds_message_bubble.widget.dart' show DSMessageBubble;
export 'src/widgets/chat/ds_message_bubble_detail.widget.dart'
show DSMessageBubbleDetail;
export 'src/widgets/chat/ds_request_location_bubble.widget.dart'
show DSRequestLocationBubble;
export 'src/widgets/chat/ds_survey_message_bubble.widget.dart'
show DSSurveyMessageBubble;
export 'src/widgets/chat/ds_text_message_bubble.widget.dart'
Expand Down Expand Up @@ -155,5 +158,3 @@ export 'src/widgets/utils/ds_group_card.widget.dart' show DSGroupCard;
export 'src/widgets/utils/ds_header.widget.dart' show DSHeader;
export 'src/widgets/utils/ds_progress_bar.widget.dart' show DSProgressBar;
export 'src/widgets/utils/ds_user_avatar.widget.dart' show DSUserAvatar;
export 'src/widgets/chat/ds_request_location_bubble.widget.dart'
show DSRequestLocationBubble;
3 changes: 1 addition & 2 deletions lib/src/controllers/chat/ds_audio_player.controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import 'package:rxdart/rxdart.dart' as rx_dart;
class DSAudioPlayerController extends GetxController {
final audioSpeed = RxDouble(1.0);
final player = AudioPlayer();

bool isInitialized = false;
final isInitialized = RxBool(false);

/// Collects the data useful for displaying in a SeekBar widget.
///
Expand Down
80 changes: 53 additions & 27 deletions lib/src/controllers/chat/ds_video_message_bubble.controller.dart
Original file line number Diff line number Diff line change
@@ -1,34 +1,37 @@
import 'dart:convert';
import 'dart:io';

import 'package:crypto/crypto.dart';
import 'package:ffmpeg_kit_flutter_full_gpl/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter_full_gpl/return_code.dart';
import 'package:file_sizes/file_sizes.dart';
import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';

import '../../models/ds_toast_props.model.dart';
import '../../services/ds_file.service.dart';
import '../../services/ds_toast.service.dart';
import '../../utils/ds_directory_formatter.util.dart';
import '../../widgets/chat/video/ds_video_error.dialog.dart';

class DSVideoMessageBubbleController {
final String uniqueId;
final String url;
final int mediaSize;
final Map<String, String?>? httpHeaders;
final String type;

DSVideoMessageBubbleController({
required this.uniqueId,
required this.url,
required this.mediaSize,
required this.type,
this.httpHeaders,
}) {
setThumbnail();
getStoredVideo();
}

final isDownloading = RxBool(false);
final thumbnail = RxString('');
final hasError = RxBool(false);
final isLoadingThumbnail = RxBool(false);

String size() {
return mediaSize > 0
Expand All @@ -40,32 +43,49 @@ class DSVideoMessageBubbleController {
: 'Download';
}

Future<void> setThumbnail() async {
final thumbnailFile = File(await getFullThumbnailPath());
if (await thumbnailFile.exists()) {
thumbnail.value = thumbnailFile.path;
Future<void> getStoredVideo() async {
try {
isLoadingThumbnail.value = true;
final fileName = md5.convert(utf8.encode(Uri.parse(url).path)).toString();
final fullPath = await DSDirectoryFormatter.getPath(
type: type,
fileName: fileName,
);
final fullThumbnailPath = await DSDirectoryFormatter.getPath(
type: 'image/png',
fileName: '$fileName-thumbnail',
);
final file = File(fullPath);
final thumbnailfile = File(fullThumbnailPath);
if (await thumbnailfile.exists()) {
thumbnail.value = thumbnailfile.path;
} else if (await file.exists() && thumbnail.value.isEmpty) {
await _generateThumbnail(file.path);
}
} finally {
isLoadingThumbnail.value = false;
}
}

Future<String> getFullThumbnailPath() async {
final temporaryPath = (await getTemporaryDirectory()).path;
return "$temporaryPath/VIDEO-Thumbnail-$uniqueId.png";
final fileName = md5.convert(utf8.encode(Uri.parse(url).path)).toString();
final mediaPath = await DSDirectoryFormatter.getPath(
type: 'image/png',
fileName: '$fileName-thumbnail',
);
return mediaPath;
}

Future<void> downloadVideo() async {
final fileName = md5.convert(utf8.encode(Uri.parse(url).path)).toString();
isDownloading.value = true;

try {
final path = Uri.parse(url).path;

var fileName = path.substring(path.lastIndexOf('/')).substring(1);

if (fileName.isEmpty) {
fileName = DateTime.now().toIso8601String();
}

final temporaryPath = (await getTemporaryDirectory()).path;
final outputFile = File('$temporaryPath/VIDEO-$uniqueId.mp4');
final fullPath = await DSDirectoryFormatter.getPath(
type: 'video/mp4',
fileName: fileName,
);
final outputFile = File(fullPath);

if (!await outputFile.exists()) {
final inputFilePath = await DSFileService.download(
Expand All @@ -77,6 +97,8 @@ class DSVideoMessageBubbleController {
final session = await FFmpegKit.execute(
'-hide_banner -y -i "$inputFilePath" "${outputFile.path}"');

File(inputFilePath!).delete();

final returnCode = await session.getReturnCode();

if (!ReturnCode.isSuccess(returnCode)) {
Expand All @@ -89,13 +111,7 @@ class DSVideoMessageBubbleController {
}
}

final thumbnailPath = await getFullThumbnailPath();

await FFmpegKit.execute(
'-hide_banner -y -i "${outputFile.path}" -vframes 1 "$thumbnailPath"',
);

thumbnail.value = thumbnailPath;
_generateThumbnail(outputFile.path);
} catch (_) {
hasError.value = true;

Expand All @@ -110,4 +126,14 @@ class DSVideoMessageBubbleController {
isDownloading.value = false;
}
}

Future<void> _generateThumbnail(String path) async {
final thumbnailPath = await getFullThumbnailPath();

await FFmpegKit.execute(
'-hide_banner -y -i "$path" -vframes 1 "$thumbnailPath"',
);

thumbnail.value = thumbnailPath;
}
}
3 changes: 0 additions & 3 deletions lib/src/controllers/ds_video_player.controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,12 @@ class DSVideoPlayerController extends GetxController {
/// the management of video controls.
DSVideoPlayerController({
required this.url,
required this.uniqueId,
this.shouldAuthenticate = false,
});

// External URL containing the video to be played
final String url;

final String uniqueId;

/// Indicates if the HTTP Requests should be authenticated or not.
final bool shouldAuthenticate;

Expand Down
34 changes: 20 additions & 14 deletions lib/src/utils/ds_directory_formatter.util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,29 @@ abstract class DSDirectoryFormatter {
required final String type,
required final String fileName,
}) async {
final String? temporaryPath = Platform.isAndroid
? (await getExternalCacheDirectories())?.first.path
: (await getApplicationCacheDirectory()).path;
final typeName = '${type.split('/').first.capitalizeFirst}';
final prefix = fileName.contains(typeName.substring(0, 3).toUpperCase())
? ''
: '${typeName.substring(0, 3).toUpperCase()}-';
final cachePath = (await getApplicationCacheDirectory()).path;

final typeFolder = '${type.split('/').first.capitalizeFirst}';
final extension = type.split('/').last;
final path =
await _formatDirectory(typeName: typeName, directory: temporaryPath!);
final fullPath = '$path/$prefix$fileName.$extension';
return fullPath;

final typePrefix = '${typeFolder.substring(0, 3).toUpperCase()}-';

final newFileName =
'${!fileName.startsWith(typePrefix) ? typePrefix : ''}$fileName';

final path = await _formatDirectory(
type: typeFolder,
directory: cachePath,
);

return '$path/$newFileName.$extension';
}

static Future<String> _formatDirectory(
{required String typeName, required String directory}) async {
final formattedDirectory = '$directory/$typeName';
static Future<String> _formatDirectory({
required final String type,
required final String directory,
}) async {
final formattedDirectory = '$directory/$type';
final directoryExists = await Directory(formattedDirectory).exists();

if (!directoryExists) {
Expand Down
1 change: 1 addition & 0 deletions lib/src/utils/ds_message_content_type.util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ abstract class DSMessageContentType {
static const String input = 'application/vnd.lime.input+json';
static const String location = 'application/vnd.lime.location+json';
static const String applicationJson = 'application/json';
static const String reply = 'application/vnd.lime.reply+json';
}
55 changes: 31 additions & 24 deletions lib/src/widgets/chat/audio/ds_audio_player.widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ class _DSAudioPlayerState extends State<DSAudioPlayer>
),
);

_controller.isInitialized = true;
_controller.isInitialized.value = true;
} catch (_) {
_controller.isInitialized = false;
_controller.isInitialized.value = false;
}
}

Expand Down Expand Up @@ -176,15 +176,17 @@ class _DSAudioPlayerState extends State<DSAudioPlayer>
final processingState = playerState?.processingState;
final playing = playerState?.playing;
if (playing != true) {
return DSPlayButton(
onPressed: _controller.isInitialized
? _controller.player.play
: () => {},
isLoading: [ProcessingState.loading, ProcessingState.buffering]
.contains(processingState),
color: _controller.isInitialized
? widget.controlForegroundColor
: DSColors.contentDisable,
return Obx(
() => DSPlayButton(
onPressed: _controller.isInitialized.value
? _controller.player.play
: () => {},
isLoading: [ProcessingState.loading, ProcessingState.buffering]
.contains(processingState),
color: _controller.isInitialized.value
? widget.controlForegroundColor
: DSColors.contentDisable,
),
);
} else if (processingState != ProcessingState.completed) {
return DSPauseButton(
Expand All @@ -202,19 +204,24 @@ class _DSAudioPlayerState extends State<DSAudioPlayer>
stream: _controller.positionDataStream,
builder: (context, snapshot) {
final positionData = snapshot.data;
return DSAudioSeekBar(
duration: positionData?.duration ?? Duration.zero,
position: positionData?.position ?? Duration.zero,
bufferedPosition: positionData?.bufferedPosition ?? Duration.zero,
onChangeEnd:
_controller.isInitialized ? _controller.player.play : null,
onChanged: _controller.isInitialized ? _controller.player.seek : null,
onChangeStart: _controller.player.pause,
labelColor: widget.labelColor,
bufferActiveTrackColor: widget.bufferActiveTrackColor,
bufferInactiveTrackColor: widget.bufferInactiveTrackColor,
sliderActiveTrackColor: widget.sliderActiveTrackColor,
sliderThumbColor: widget.sliderThumbColor,
return Obx(
() => DSAudioSeekBar(
duration: positionData?.duration ?? Duration.zero,
position: positionData?.position ?? Duration.zero,
bufferedPosition: positionData?.bufferedPosition ?? Duration.zero,
onChangeEnd: _controller.isInitialized.value
? _controller.player.play
: null,
onChanged: _controller.isInitialized.value
? _controller.player.seek
: null,
onChangeStart: _controller.player.pause,
labelColor: widget.labelColor,
bufferActiveTrackColor: widget.bufferActiveTrackColor,
bufferInactiveTrackColor: widget.bufferInactiveTrackColor,
sliderActiveTrackColor: widget.sliderActiveTrackColor,
sliderThumbColor: widget.sliderThumbColor,
),
);
},
);
Expand Down
3 changes: 0 additions & 3 deletions lib/src/widgets/chat/video/ds_video_body.widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ class DSVideoBody extends StatelessWidget {
required this.appBarText,
required this.appBarPhotoUri,
required this.url,
required this.uniqueId,
required this.thumbnail,
this.shouldAuthenticate = false,
});
Expand All @@ -19,7 +18,6 @@ class DSVideoBody extends StatelessWidget {
final String appBarText;
final Uri? appBarPhotoUri;
final String url;
final String uniqueId;
final Widget thumbnail;
final bool shouldAuthenticate;

Expand All @@ -43,7 +41,6 @@ class DSVideoBody extends StatelessWidget {
appBarText: appBarText,
appBarPhotoUri: appBarPhotoUri,
url: url,
uniqueId: uniqueId,
shouldAuthenticate: shouldAuthenticate,
),
);
Expand Down
Loading

0 comments on commit f43658f

Please sign in to comment.